@memberjunction/ng-entity-viewer 6.1.4 → 6.2.0-edge.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts +18 -2
- package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts.map +1 -1
- package/dist/lib/aggregate-panel/aggregate-panel.component.js +47 -18
- package/dist/lib/aggregate-panel/aggregate-panel.component.js.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts +22 -0
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +57 -13
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.d.ts +68 -5
- package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.js +213 -73
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +156 -14
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.js +432 -138
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
- package/dist/lib/entity-data-grid/events/grid-events.js +67 -67
- package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +112 -8
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +255 -69
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +11 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +45 -8
- package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
- package/dist/lib/pill/pill.component.d.ts +13 -3
- package/dist/lib/pill/pill.component.d.ts.map +1 -1
- package/dist/lib/pill/pill.component.js +34 -10
- package/dist/lib/pill/pill.component.js.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts +29 -3
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +173 -114
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts +4 -0
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.js +10 -2
- package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -1
- package/dist/lib/utils/aggregate-field.util.d.ts +21 -0
- package/dist/lib/utils/aggregate-field.util.d.ts.map +1 -0
- package/dist/lib/utils/aggregate-field.util.js +40 -0
- package/dist/lib/utils/aggregate-field.util.js.map +1 -0
- package/dist/lib/utils/highlight.util.d.ts +6 -0
- package/dist/lib/utils/highlight.util.d.ts.map +1 -1
- package/dist/lib/utils/highlight.util.js +23 -11
- package/dist/lib/utils/highlight.util.js.map +1 -1
- package/dist/lib/utils/record.util.d.ts +10 -0
- package/dist/lib/utils/record.util.d.ts.map +1 -1
- package/dist/lib/utils/record.util.js +20 -4
- package/dist/lib/utils/record.util.js.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts +22 -2
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.js +54 -14
- package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
- package/dist/lib/view-selector/view-selector.component.js +2 -2
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts +15 -3
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js +33 -5
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +160 -5
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js +576 -36
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts +16 -2
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.js +37 -9
- package/dist/lib/view-types/renderers/map-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/view-type.contracts.js.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.d.ts +235 -24
- package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.js +600 -174
- package/dist/lib/view-workspace/view-workspace.component.js.map +1 -1
- package/dist/module.d.ts +5 -4
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +8 -1
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +21 -20
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
|
|
2
2
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
3
|
-
import { EntityFieldValueListType, RunView, CoerceImageSrc } from '@memberjunction/core';
|
|
4
|
-
import {
|
|
3
|
+
import { EntityFieldValueListType, RunView, CoerceImageSrc, IsDateOnlySQLType, FormatDateOnly } from '@memberjunction/core';
|
|
4
|
+
import { BuildCompositeKey, BuildPkString, ComputeFieldsList } from '../utils/record.util';
|
|
5
5
|
import { PillColorUtil } from '../pill/pill.component';
|
|
6
6
|
import { HighlightUtil } from '../utils/highlight.util';
|
|
7
7
|
import * as i0 from "@angular/core";
|
|
@@ -203,11 +203,14 @@ function EntityCardsComponent_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1
|
|
|
203
203
|
} }
|
|
204
204
|
function EntityCardsComponent_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
205
205
|
i0.ɵɵelement(0, "mj-empty-state", 5);
|
|
206
|
+
} if (rf & 2) {
|
|
207
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
208
|
+
i0.ɵɵproperty("Title", ctx_r2.entity ? "No " + ctx_r2.entity.DisplayNamePlural + " to display" : "No records to display");
|
|
206
209
|
} }
|
|
207
210
|
function EntityCardsComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
208
211
|
i0.ɵɵelementStart(0, "div", 2);
|
|
209
212
|
i0.ɵɵrepeaterCreate(1, EntityCardsComponent_Conditional_2_For_2_Template, 15, 9, "div", 4, _forTrack0);
|
|
210
|
-
i0.ɵɵconditionalCreate(3, EntityCardsComponent_Conditional_2_Conditional_3_Template, 1,
|
|
213
|
+
i0.ɵɵconditionalCreate(3, EntityCardsComponent_Conditional_2_Conditional_3_Template, 1, 1, "mj-empty-state", 5);
|
|
211
214
|
i0.ɵɵelementEnd();
|
|
212
215
|
} if (rf & 2) {
|
|
213
216
|
const ctx_r2 = i0.ɵɵnextContext();
|
|
@@ -260,12 +263,28 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
260
263
|
/**
|
|
261
264
|
* Custom card template (optional - auto-generated if not provided)
|
|
262
265
|
*/
|
|
263
|
-
|
|
266
|
+
CardTemplate = null;
|
|
267
|
+
/** @deprecated Use {@link CardTemplate}. */
|
|
268
|
+
set cardTemplate(value) {
|
|
269
|
+
this.CardTemplate = value;
|
|
270
|
+
}
|
|
271
|
+
/** @deprecated Use {@link CardTemplate}. */
|
|
272
|
+
get cardTemplate() {
|
|
273
|
+
return this.CardTemplate;
|
|
274
|
+
}
|
|
264
275
|
/**
|
|
265
276
|
* Map of record IDs to hidden field names that matched the filter
|
|
266
277
|
* Used to display an indicator when a match occurred in a non-visible field
|
|
267
278
|
*/
|
|
268
|
-
|
|
279
|
+
HiddenFieldMatches = new Map();
|
|
280
|
+
/** @deprecated Use {@link HiddenFieldMatches}. */
|
|
281
|
+
set hiddenFieldMatches(value) {
|
|
282
|
+
this.HiddenFieldMatches = value;
|
|
283
|
+
}
|
|
284
|
+
/** @deprecated Use {@link HiddenFieldMatches}. */
|
|
285
|
+
get hiddenFieldMatches() {
|
|
286
|
+
return this.HiddenFieldMatches;
|
|
287
|
+
}
|
|
269
288
|
/**
|
|
270
289
|
* Current filter text for highlighting matches
|
|
271
290
|
* Supports SQL-style % wildcards
|
|
@@ -285,7 +304,15 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
285
304
|
*/
|
|
286
305
|
recordOpened = new EventEmitter();
|
|
287
306
|
/** Auto-generated card template */
|
|
288
|
-
|
|
307
|
+
AutoCardTemplate = null;
|
|
308
|
+
/** @deprecated Use {@link AutoCardTemplate}. */
|
|
309
|
+
get autoCardTemplate() {
|
|
310
|
+
return this.AutoCardTemplate;
|
|
311
|
+
}
|
|
312
|
+
/** @deprecated Use {@link AutoCardTemplate}. */
|
|
313
|
+
set autoCardTemplate(value) {
|
|
314
|
+
this.AutoCardTemplate = value;
|
|
315
|
+
}
|
|
289
316
|
/** Internal records when loading standalone */
|
|
290
317
|
internalRecords = [];
|
|
291
318
|
/** Track if we're in standalone mode */
|
|
@@ -301,11 +328,19 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
301
328
|
* buildPkString() allocations + 4 RegExp-building highlight calls that the
|
|
302
329
|
* previous per-card-per-CD method bindings incurred.
|
|
303
330
|
*/
|
|
304
|
-
|
|
331
|
+
CardViewModels = [];
|
|
332
|
+
/** @deprecated Use {@link CardViewModels}. */
|
|
333
|
+
get cardViewModels() {
|
|
334
|
+
return this.CardViewModels;
|
|
335
|
+
}
|
|
336
|
+
/** @deprecated Use {@link CardViewModels}. */
|
|
337
|
+
set cardViewModels(value) {
|
|
338
|
+
this.CardViewModels = value;
|
|
339
|
+
}
|
|
305
340
|
ngOnInit() {
|
|
306
341
|
this.standaloneMode = this.records === null;
|
|
307
|
-
if (this.entity?.Fields && !this.
|
|
308
|
-
this.
|
|
342
|
+
if (this.entity?.Fields && !this.EffectiveTemplate) {
|
|
343
|
+
this.AutoCardTemplate = this.generateCardTemplate(this.entity);
|
|
309
344
|
}
|
|
310
345
|
if (this.standaloneMode && this.entity) {
|
|
311
346
|
this.loadData();
|
|
@@ -314,10 +349,10 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
314
349
|
}
|
|
315
350
|
ngOnChanges(changes) {
|
|
316
351
|
if (changes['entity'] && this.entity?.Fields) {
|
|
317
|
-
this.
|
|
352
|
+
this.AutoCardTemplate = this.generateCardTemplate(this.entity);
|
|
318
353
|
}
|
|
319
354
|
else if (changes['entity'] && !this.entity) {
|
|
320
|
-
this.
|
|
355
|
+
this.AutoCardTemplate = null;
|
|
321
356
|
}
|
|
322
357
|
if (changes['entity'] && this.standaloneMode && this.entity) {
|
|
323
358
|
this.loadData();
|
|
@@ -374,9 +409,13 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
374
409
|
/**
|
|
375
410
|
* Get effective records (external or internal)
|
|
376
411
|
*/
|
|
377
|
-
get
|
|
412
|
+
get EffectiveRecords() {
|
|
378
413
|
return this.records ?? this.internalRecords;
|
|
379
414
|
}
|
|
415
|
+
/** @deprecated Use {@link EffectiveRecords}. */
|
|
416
|
+
get effectiveRecords() {
|
|
417
|
+
return this.EffectiveRecords;
|
|
418
|
+
}
|
|
380
419
|
/**
|
|
381
420
|
* Load data in standalone mode
|
|
382
421
|
*/
|
|
@@ -389,7 +428,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
389
428
|
const result = await rv.RunView({
|
|
390
429
|
EntityName: this.entity.Name,
|
|
391
430
|
ResultType: 'simple',
|
|
392
|
-
Fields:
|
|
431
|
+
Fields: ComputeFieldsList(this.entity),
|
|
393
432
|
MaxRows: this.pageSize
|
|
394
433
|
});
|
|
395
434
|
if (result.Success) {
|
|
@@ -407,8 +446,12 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
407
446
|
/**
|
|
408
447
|
* Get the effective card template (custom or auto-generated)
|
|
409
448
|
*/
|
|
449
|
+
get EffectiveTemplate() {
|
|
450
|
+
return this.CardTemplate || this.AutoCardTemplate;
|
|
451
|
+
}
|
|
452
|
+
/** @deprecated Use {@link EffectiveTemplate}. */
|
|
410
453
|
get effectiveTemplate() {
|
|
411
|
-
return this.
|
|
454
|
+
return this.EffectiveTemplate;
|
|
412
455
|
}
|
|
413
456
|
// ========================================
|
|
414
457
|
// TEMPLATE GENERATION
|
|
@@ -491,7 +534,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
491
534
|
displayFields.push({
|
|
492
535
|
name: field.Name,
|
|
493
536
|
type: this.getFieldType(field),
|
|
494
|
-
label: this.
|
|
537
|
+
label: this.GetFieldLabel(field)
|
|
495
538
|
});
|
|
496
539
|
}
|
|
497
540
|
if (displayFields.length >= 2)
|
|
@@ -506,7 +549,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
506
549
|
displayFields.push({
|
|
507
550
|
name: field.Name,
|
|
508
551
|
type: this.getFieldType(field),
|
|
509
|
-
label: this.
|
|
552
|
+
label: this.GetFieldLabel(field)
|
|
510
553
|
});
|
|
511
554
|
}
|
|
512
555
|
}
|
|
@@ -547,7 +590,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
547
590
|
// ========================================
|
|
548
591
|
// VALUE FORMATTING
|
|
549
592
|
// ========================================
|
|
550
|
-
|
|
593
|
+
GetFieldValue(record, fieldName) {
|
|
551
594
|
if (!fieldName)
|
|
552
595
|
return '';
|
|
553
596
|
const value = record[fieldName];
|
|
@@ -555,7 +598,11 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
555
598
|
return '';
|
|
556
599
|
return String(value);
|
|
557
600
|
}
|
|
558
|
-
|
|
601
|
+
/** @deprecated Use {@link GetFieldValue}. */
|
|
602
|
+
getFieldValue(record, fieldName) {
|
|
603
|
+
return this.GetFieldValue(record, fieldName);
|
|
604
|
+
}
|
|
605
|
+
GetNumericValue(record, fieldName) {
|
|
559
606
|
const value = record[fieldName];
|
|
560
607
|
if (value === null || value === undefined)
|
|
561
608
|
return '-';
|
|
@@ -572,7 +619,11 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
572
619
|
}
|
|
573
620
|
return num.toLocaleString();
|
|
574
621
|
}
|
|
575
|
-
|
|
622
|
+
/** @deprecated Use {@link GetNumericValue}. */
|
|
623
|
+
getNumericValue(record, fieldName) {
|
|
624
|
+
return this.GetNumericValue(record, fieldName);
|
|
625
|
+
}
|
|
626
|
+
GetBooleanValue(record, fieldName) {
|
|
576
627
|
const value = record[fieldName];
|
|
577
628
|
if (value === null || value === undefined)
|
|
578
629
|
return false;
|
|
@@ -584,15 +635,23 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
584
635
|
return value.toLowerCase() === 'true' || value === '1';
|
|
585
636
|
return Boolean(value);
|
|
586
637
|
}
|
|
587
|
-
|
|
588
|
-
|
|
638
|
+
/** @deprecated Use {@link GetBooleanValue}. */
|
|
639
|
+
getBooleanValue(record, fieldName) {
|
|
640
|
+
return this.GetBooleanValue(record, fieldName);
|
|
641
|
+
}
|
|
642
|
+
GetTextValue(record, fieldName, maxLength = 50) {
|
|
643
|
+
const value = this.GetFieldValue(record, fieldName);
|
|
589
644
|
if (!value)
|
|
590
645
|
return '-';
|
|
591
646
|
if (value.length <= maxLength)
|
|
592
647
|
return value;
|
|
593
648
|
return value.substring(0, maxLength) + '...';
|
|
594
649
|
}
|
|
595
|
-
|
|
650
|
+
/** @deprecated Use {@link GetTextValue}. */
|
|
651
|
+
getTextValue(record, fieldName, maxLength = 50) {
|
|
652
|
+
return this.GetTextValue(record, fieldName, maxLength);
|
|
653
|
+
}
|
|
654
|
+
GetDateValue(record, fieldName) {
|
|
596
655
|
const value = record[fieldName];
|
|
597
656
|
if (value === null || value === undefined)
|
|
598
657
|
return '-';
|
|
@@ -600,56 +659,89 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
600
659
|
const date = value instanceof Date ? value : new Date(value);
|
|
601
660
|
if (isNaN(date.getTime()))
|
|
602
661
|
return String(value);
|
|
603
|
-
|
|
662
|
+
const options = { month: 'short', day: 'numeric', year: 'numeric' };
|
|
663
|
+
// A `date` column is a calendar day that arrives as UTC midnight; a local-zone formatter
|
|
664
|
+
// would land on the previous day for every reader west of Greenwich (MJ#4210). A timestamp
|
|
665
|
+
// names an instant and stays in local time.
|
|
666
|
+
if (this.isDateOnlyField(fieldName))
|
|
667
|
+
return FormatDateOnly(date, options);
|
|
668
|
+
return date.toLocaleDateString(undefined, options);
|
|
604
669
|
}
|
|
605
670
|
catch {
|
|
606
671
|
return String(value);
|
|
607
672
|
}
|
|
608
673
|
}
|
|
674
|
+
/** @deprecated Use {@link GetDateValue}. */
|
|
675
|
+
getDateValue(record, fieldName) {
|
|
676
|
+
return this.GetDateValue(record, fieldName);
|
|
677
|
+
}
|
|
678
|
+
isDateOnlyField(fieldName) {
|
|
679
|
+
return IsDateOnlySQLType(this.entity?.Fields.find(f => f.Name === fieldName)?.Type);
|
|
680
|
+
}
|
|
609
681
|
/**
|
|
610
682
|
* Get display label for a field using EntityFieldInfo's built-in DisplayNameOrName property
|
|
611
683
|
*/
|
|
612
|
-
|
|
684
|
+
GetFieldLabel(field) {
|
|
613
685
|
return field.DisplayNameOrName;
|
|
614
686
|
}
|
|
687
|
+
/** @deprecated Use {@link GetFieldLabel}. */
|
|
688
|
+
getFieldLabel(field) {
|
|
689
|
+
return this.GetFieldLabel(field);
|
|
690
|
+
}
|
|
615
691
|
// ========================================
|
|
616
692
|
// CARD DISPLAY HELPERS
|
|
617
693
|
// ========================================
|
|
618
|
-
|
|
694
|
+
GetRecordTrackId(record, index) {
|
|
619
695
|
if (!this.entity)
|
|
620
696
|
return `record_${index}`;
|
|
621
697
|
try {
|
|
622
|
-
const pk =
|
|
698
|
+
const pk = BuildPkString(record, this.entity);
|
|
623
699
|
if (pk && pk.trim().length > 0)
|
|
624
700
|
return pk;
|
|
625
701
|
}
|
|
626
702
|
catch { /* ignore */ }
|
|
627
703
|
return `record_${index}`;
|
|
628
704
|
}
|
|
629
|
-
|
|
705
|
+
/** @deprecated Use {@link GetRecordTrackId}. */
|
|
706
|
+
getRecordTrackId(record, index) {
|
|
707
|
+
return this.GetRecordTrackId(record, index);
|
|
708
|
+
}
|
|
709
|
+
IsSelected(record) {
|
|
630
710
|
if (!this.entity)
|
|
631
711
|
return false;
|
|
632
|
-
return
|
|
712
|
+
return BuildPkString(record, this.entity) === this.selectedRecordId;
|
|
633
713
|
}
|
|
634
|
-
|
|
714
|
+
/** @deprecated Use {@link IsSelected}. */
|
|
715
|
+
isSelected(record) {
|
|
716
|
+
return this.IsSelected(record);
|
|
717
|
+
}
|
|
718
|
+
OnCardClick(record) {
|
|
635
719
|
if (!this.entity)
|
|
636
720
|
return;
|
|
637
721
|
this.recordSelected.emit({
|
|
638
722
|
record,
|
|
639
723
|
entity: this.entity,
|
|
640
|
-
compositeKey:
|
|
724
|
+
compositeKey: BuildCompositeKey(record, this.entity)
|
|
641
725
|
});
|
|
642
726
|
}
|
|
643
|
-
|
|
727
|
+
/** @deprecated Use {@link OnCardClick}. */
|
|
728
|
+
onCardClick(record) {
|
|
729
|
+
return this.OnCardClick(record);
|
|
730
|
+
}
|
|
731
|
+
OnOpenClick(event, record) {
|
|
644
732
|
event.stopPropagation();
|
|
645
733
|
if (!this.entity)
|
|
646
734
|
return;
|
|
647
735
|
this.recordOpened.emit({
|
|
648
736
|
record,
|
|
649
737
|
entity: this.entity,
|
|
650
|
-
compositeKey:
|
|
738
|
+
compositeKey: BuildCompositeKey(record, this.entity)
|
|
651
739
|
});
|
|
652
740
|
}
|
|
741
|
+
/** @deprecated Use {@link OnOpenClick}. */
|
|
742
|
+
onOpenClick(event, record) {
|
|
743
|
+
return this.OnOpenClick(event, record);
|
|
744
|
+
}
|
|
653
745
|
// ========================================
|
|
654
746
|
// PRECOMPUTED VIEW-MODELS (perf)
|
|
655
747
|
// ========================================
|
|
@@ -660,8 +752,8 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
660
752
|
* template method bindings avoids re-running it on every Angular CD cycle.
|
|
661
753
|
*/
|
|
662
754
|
buildCardViewModels() {
|
|
663
|
-
const records = this.
|
|
664
|
-
this.
|
|
755
|
+
const records = this.EffectiveRecords;
|
|
756
|
+
this.CardViewModels = records.map((record, index) => this.buildCardViewModel(record, index));
|
|
665
757
|
}
|
|
666
758
|
/**
|
|
667
759
|
* Cheaply update the isSelected flag on the EXISTING card view-models in place,
|
|
@@ -672,7 +764,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
672
764
|
* compare per card with zero allocation or regex work.
|
|
673
765
|
*/
|
|
674
766
|
updateSelectionFlags() {
|
|
675
|
-
for (const vm of this.
|
|
767
|
+
for (const vm of this.CardViewModels) {
|
|
676
768
|
vm.isSelected = vm.pkString.length > 0 && vm.pkString === this.selectedRecordId;
|
|
677
769
|
}
|
|
678
770
|
}
|
|
@@ -683,9 +775,9 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
683
775
|
buildCardViewModel(record, index) {
|
|
684
776
|
const pkString = this.computePkString(record);
|
|
685
777
|
const trackId = pkString && pkString.trim().length > 0 ? pkString : `record_${index}`;
|
|
686
|
-
const template = this.
|
|
778
|
+
const template = this.EffectiveTemplate;
|
|
687
779
|
const subtitleField = template?.subtitleField ?? null;
|
|
688
|
-
const subtitleValue = subtitleField ? this.
|
|
780
|
+
const subtitleValue = subtitleField ? this.GetFieldValue(record, subtitleField) : '';
|
|
689
781
|
const descriptionField = template?.descriptionField ?? null;
|
|
690
782
|
return {
|
|
691
783
|
record,
|
|
@@ -693,17 +785,17 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
693
785
|
trackId,
|
|
694
786
|
isSelected: pkString.length > 0 && pkString === this.selectedRecordId,
|
|
695
787
|
color: this.computeRecordColor(pkString),
|
|
696
|
-
thumbnailType: this.
|
|
697
|
-
thumbnailUrl: this.
|
|
698
|
-
initials: this.
|
|
699
|
-
highlightedTitle: this.
|
|
788
|
+
thumbnailType: this.GetThumbnailType(record),
|
|
789
|
+
thumbnailUrl: this.GetThumbnailUrl(record),
|
|
790
|
+
initials: this.GetInitials(record),
|
|
791
|
+
highlightedTitle: this.HighlightMatch(this.GetCombinedTitle(record)),
|
|
700
792
|
subtitleValue,
|
|
701
|
-
highlightedSubtitle: this.
|
|
793
|
+
highlightedSubtitle: this.HighlightMatch(subtitleValue),
|
|
702
794
|
highlightedDescription: descriptionField
|
|
703
|
-
? this.
|
|
795
|
+
? this.HighlightMatch(this.GetTextValue(record, descriptionField, 100))
|
|
704
796
|
: '',
|
|
705
797
|
displayFields: this.buildDisplayFieldVMs(record, template),
|
|
706
|
-
badgeValue: template?.badgeField ? this.
|
|
798
|
+
badgeValue: template?.badgeField ? this.GetFieldValue(record, template.badgeField) : '',
|
|
707
799
|
hasHiddenFieldMatch: this.computeHasHiddenFieldMatch(pkString),
|
|
708
800
|
hiddenMatchFieldName: this.computeHiddenMatchFieldName(pkString)
|
|
709
801
|
};
|
|
@@ -716,10 +808,10 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
716
808
|
return [];
|
|
717
809
|
return template.displayFields.map(field => ({
|
|
718
810
|
field,
|
|
719
|
-
booleanValue: this.
|
|
720
|
-
numericValue: this.
|
|
721
|
-
dateValue: this.
|
|
722
|
-
highlightedText: this.
|
|
811
|
+
booleanValue: this.GetBooleanValue(record, field.name),
|
|
812
|
+
numericValue: this.GetNumericValue(record, field.name),
|
|
813
|
+
dateValue: this.GetDateValue(record, field.name),
|
|
814
|
+
highlightedText: this.HighlightMatch(this.GetTextValue(record, field.name, 40))
|
|
723
815
|
}));
|
|
724
816
|
}
|
|
725
817
|
/**
|
|
@@ -730,7 +822,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
730
822
|
if (!this.entity)
|
|
731
823
|
return '';
|
|
732
824
|
try {
|
|
733
|
-
return
|
|
825
|
+
return BuildPkString(record, this.entity);
|
|
734
826
|
}
|
|
735
827
|
catch {
|
|
736
828
|
return '';
|
|
@@ -755,7 +847,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
755
847
|
computeHasHiddenFieldMatch(pkString) {
|
|
756
848
|
if (!this.entity || !pkString)
|
|
757
849
|
return false;
|
|
758
|
-
return this.
|
|
850
|
+
return this.HiddenFieldMatches.has(pkString);
|
|
759
851
|
}
|
|
760
852
|
/**
|
|
761
853
|
* Resolve the display name of the hidden field that matched, from a PK string.
|
|
@@ -763,7 +855,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
763
855
|
computeHiddenMatchFieldName(pkString) {
|
|
764
856
|
if (!this.entity || !pkString)
|
|
765
857
|
return '';
|
|
766
|
-
const fieldName = this.
|
|
858
|
+
const fieldName = this.HiddenFieldMatches.get(pkString);
|
|
767
859
|
if (!fieldName)
|
|
768
860
|
return '';
|
|
769
861
|
const field = this.entity.Fields.find(f => f.Name === fieldName);
|
|
@@ -773,17 +865,21 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
773
865
|
* Get the combined title from all title fields for a record.
|
|
774
866
|
* Joins multiple IsNameField values with spaces (e.g., "Elizabeth Rodriguez").
|
|
775
867
|
*/
|
|
776
|
-
|
|
777
|
-
const template = this.
|
|
868
|
+
GetCombinedTitle(record) {
|
|
869
|
+
const template = this.EffectiveTemplate;
|
|
778
870
|
if (!template?.titleFields || template.titleFields.length === 0)
|
|
779
871
|
return '';
|
|
780
872
|
const parts = template.titleFields
|
|
781
|
-
.map(f => this.
|
|
873
|
+
.map(f => this.GetFieldValue(record, f))
|
|
782
874
|
.filter(v => v && v.trim().length > 0);
|
|
783
875
|
return parts.length > 0 ? parts.join(' ') : '';
|
|
784
876
|
}
|
|
785
|
-
|
|
786
|
-
|
|
877
|
+
/** @deprecated Use {@link GetCombinedTitle}. */
|
|
878
|
+
getCombinedTitle(record) {
|
|
879
|
+
return this.GetCombinedTitle(record);
|
|
880
|
+
}
|
|
881
|
+
GetInitials(record) {
|
|
882
|
+
const title = this.GetCombinedTitle(record);
|
|
787
883
|
if (!title)
|
|
788
884
|
return '?';
|
|
789
885
|
const words = title.split(/\s+/).filter(w => w.length > 0);
|
|
@@ -791,10 +887,14 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
791
887
|
return words[0].substring(0, 2).toUpperCase();
|
|
792
888
|
return (words[0][0] + words[words.length - 1][0]).toUpperCase();
|
|
793
889
|
}
|
|
890
|
+
/** @deprecated Use {@link GetInitials}. */
|
|
891
|
+
getInitials(record) {
|
|
892
|
+
return this.GetInitials(record);
|
|
893
|
+
}
|
|
794
894
|
/**
|
|
795
895
|
* Get the thumbnail type for a record, with per-record fallback through thumbnailFields
|
|
796
896
|
*/
|
|
797
|
-
|
|
897
|
+
GetThumbnailType(record) {
|
|
798
898
|
const fieldInfo = this.getEffectiveThumbnailField(record);
|
|
799
899
|
if (!fieldInfo)
|
|
800
900
|
return 'none';
|
|
@@ -810,26 +910,34 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
810
910
|
return 'icon';
|
|
811
911
|
return 'none';
|
|
812
912
|
}
|
|
913
|
+
/** @deprecated Use {@link GetThumbnailType}. */
|
|
914
|
+
getThumbnailType(record) {
|
|
915
|
+
return this.GetThumbnailType(record);
|
|
916
|
+
}
|
|
813
917
|
/**
|
|
814
918
|
* Get the thumbnail URL/value for a record, with per-record fallback
|
|
815
919
|
*/
|
|
816
|
-
|
|
920
|
+
GetThumbnailUrl(record) {
|
|
817
921
|
const fieldInfo = this.getEffectiveThumbnailField(record);
|
|
818
922
|
if (!fieldInfo)
|
|
819
923
|
return '';
|
|
820
924
|
return CoerceImageSrc(fieldInfo.value) || fieldInfo.value;
|
|
821
925
|
}
|
|
926
|
+
/** @deprecated Use {@link GetThumbnailUrl}. */
|
|
927
|
+
getThumbnailUrl(record) {
|
|
928
|
+
return this.GetThumbnailUrl(record);
|
|
929
|
+
}
|
|
822
930
|
/**
|
|
823
931
|
* Find the first thumbnail field that has a value for this record
|
|
824
932
|
* Returns both the field name and value for type determination
|
|
825
933
|
*/
|
|
826
934
|
getEffectiveThumbnailField(record) {
|
|
827
|
-
const template = this.
|
|
935
|
+
const template = this.EffectiveTemplate;
|
|
828
936
|
if (!template?.thumbnailFields || template.thumbnailFields.length === 0)
|
|
829
937
|
return null;
|
|
830
938
|
// Try each field in priority order until we find one with a value
|
|
831
939
|
for (const fieldName of template.thumbnailFields) {
|
|
832
|
-
const value = this.
|
|
940
|
+
const value = this.GetFieldValue(record, fieldName);
|
|
833
941
|
if (value && value.trim() !== '') {
|
|
834
942
|
return { fieldName, value };
|
|
835
943
|
}
|
|
@@ -853,10 +961,14 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
853
961
|
return true;
|
|
854
962
|
return false;
|
|
855
963
|
}
|
|
856
|
-
|
|
964
|
+
GetRecordColor(record) {
|
|
857
965
|
return this.computeRecordColor(this.computePkString(record));
|
|
858
966
|
}
|
|
859
|
-
|
|
967
|
+
/** @deprecated Use {@link GetRecordColor}. */
|
|
968
|
+
getRecordColor(record) {
|
|
969
|
+
return this.GetRecordColor(record);
|
|
970
|
+
}
|
|
971
|
+
IsEnumField(fieldName) {
|
|
860
972
|
if (!this.entity)
|
|
861
973
|
return false;
|
|
862
974
|
const field = this.entity.Fields.find(f => f.Name === fieldName);
|
|
@@ -864,45 +976,69 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
864
976
|
return false;
|
|
865
977
|
return field.ValueListTypeEnum !== EntityFieldValueListType.None && field.EntityFieldValues.length > 0;
|
|
866
978
|
}
|
|
867
|
-
|
|
868
|
-
|
|
979
|
+
/** @deprecated Use {@link IsEnumField}. */
|
|
980
|
+
isEnumField(fieldName) {
|
|
981
|
+
return this.IsEnumField(fieldName);
|
|
982
|
+
}
|
|
983
|
+
get SubtitleIsPill() {
|
|
984
|
+
const template = this.EffectiveTemplate;
|
|
869
985
|
if (!template?.subtitleField || !this.entity)
|
|
870
986
|
return false;
|
|
871
|
-
return this.
|
|
987
|
+
return this.IsEnumField(template.subtitleField);
|
|
872
988
|
}
|
|
873
|
-
|
|
989
|
+
/** @deprecated Use {@link SubtitleIsPill}. */
|
|
990
|
+
get subtitleIsPill() {
|
|
991
|
+
return this.SubtitleIsPill;
|
|
992
|
+
}
|
|
993
|
+
GetPillColorType(value) {
|
|
874
994
|
return PillColorUtil.getColorType(value);
|
|
875
995
|
}
|
|
996
|
+
/** @deprecated Use {@link GetPillColorType}. */
|
|
997
|
+
getPillColorType(value) {
|
|
998
|
+
return this.GetPillColorType(value);
|
|
999
|
+
}
|
|
876
1000
|
/**
|
|
877
1001
|
* Check if a record matched on a hidden field
|
|
878
1002
|
*/
|
|
879
|
-
|
|
1003
|
+
HasHiddenFieldMatch(record) {
|
|
880
1004
|
if (!this.entity)
|
|
881
1005
|
return false;
|
|
882
|
-
return this.
|
|
1006
|
+
return this.HiddenFieldMatches.has(BuildPkString(record, this.entity));
|
|
1007
|
+
}
|
|
1008
|
+
/** @deprecated Use {@link HasHiddenFieldMatch}. */
|
|
1009
|
+
hasHiddenFieldMatch(record) {
|
|
1010
|
+
return this.HasHiddenFieldMatch(record);
|
|
883
1011
|
}
|
|
884
1012
|
/**
|
|
885
1013
|
* Get the display name of the hidden field that matched
|
|
886
1014
|
*/
|
|
887
|
-
|
|
1015
|
+
GetHiddenMatchFieldName(record) {
|
|
888
1016
|
if (!this.entity)
|
|
889
1017
|
return '';
|
|
890
|
-
const fieldName = this.
|
|
1018
|
+
const fieldName = this.HiddenFieldMatches.get(BuildPkString(record, this.entity));
|
|
891
1019
|
if (!fieldName || !this.entity)
|
|
892
1020
|
return '';
|
|
893
1021
|
// Look up the field in entity metadata and use DisplayNameOrName
|
|
894
1022
|
const field = this.entity.Fields.find(f => f.Name === fieldName);
|
|
895
1023
|
return field ? field.DisplayNameOrName : fieldName;
|
|
896
1024
|
}
|
|
1025
|
+
/** @deprecated Use {@link GetHiddenMatchFieldName}. */
|
|
1026
|
+
getHiddenMatchFieldName(record) {
|
|
1027
|
+
return this.GetHiddenMatchFieldName(record);
|
|
1028
|
+
}
|
|
897
1029
|
/**
|
|
898
1030
|
* Highlight matching text in a string based on the filter text
|
|
899
1031
|
* Uses HighlightUtil which only highlights if the text actually matches the pattern
|
|
900
1032
|
*/
|
|
901
|
-
|
|
1033
|
+
HighlightMatch(text) {
|
|
902
1034
|
return HighlightUtil.highlight(text, this.filterText, false);
|
|
903
1035
|
}
|
|
1036
|
+
/** @deprecated Use {@link HighlightMatch}. */
|
|
1037
|
+
highlightMatch(text) {
|
|
1038
|
+
return this.HighlightMatch(text);
|
|
1039
|
+
}
|
|
904
1040
|
static ɵfac = function EntityCardsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EntityCardsComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
905
|
-
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" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, 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"], ["Variant", "no-results",
|
|
1041
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityCardsComponent, selectors: [["mj-entity-cards"]], inputs: { entity: "entity", records: "records", selectedRecordId: "selectedRecordId", CardTemplate: "CardTemplate", cardTemplate: "cardTemplate", HiddenFieldMatches: "HiddenFieldMatches", hiddenFieldMatches: "hiddenFieldMatches", filterText: "filterText", pageSize: "pageSize" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, 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"], ["Variant", "no-results", 1, "no-results", 3, "Title"], [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"]], template: function EntityCardsComponent_Template(rf, ctx) { if (rf & 1) {
|
|
906
1042
|
i0.ɵɵelementStart(0, "div", 0);
|
|
907
1043
|
i0.ɵɵconditionalCreate(1, EntityCardsComponent_Conditional_1_Template, 2, 0, "div", 1)(2, EntityCardsComponent_Conditional_2_Template, 4, 1, "div", 2);
|
|
908
1044
|
i0.ɵɵelementEnd();
|
|
@@ -913,15 +1049,19 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
913
1049
|
}
|
|
914
1050
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityCardsComponent, [{
|
|
915
1051
|
type: Component,
|
|
916
|
-
args: [{ standalone: false, selector: 'mj-entity-cards', encapsulation: ViewEncapsulation.None, 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 (vm of cardViewModels; track vm.trackId) {\n <div\n class=\"data-card\"\n [class.selected]=\"vm.isSelected\"\n (click)=\"onCardClick(vm.record)\">\n\n <!-- Card Header with Avatar/Icon -->\n <div class=\"card-header\">\n @switch (vm.thumbnailType) {\n @case ('image') {\n <div class=\"card-thumbnail\">\n <img [src]=\"vm.thumbnailUrl\" alt=\"\" />\n </div>\n }\n @case ('icon') {\n <div class=\"card-avatar\" [style.background-color]=\"vm.color\">\n <i [class]=\"vm.thumbnailUrl\"></i>\n </div>\n }\n @default {\n <div class=\"card-avatar\" [style.background-color]=\"vm.color\">\n {{ vm.initials }}\n </div>\n }\n }\n\n <div class=\"card-header-content\">\n <h3 class=\"card-title\" [innerHTML]=\"vm.highlightedTitle\"></h3>\n @if (effectiveTemplate?.subtitleField) {\n @if (subtitleIsPill) {\n <mj-pill [value]=\"vm.subtitleValue\"></mj-pill>\n } @else {\n <span class=\"card-subtitle\" [innerHTML]=\"vm.highlightedSubtitle\"></span>\n }\n }\n </div>\n\n <button class=\"card-open-btn\" (click)=\"onOpenClick($event, vm.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) {\n <p class=\"card-description\" [innerHTML]=\"vm.highlightedDescription\"></p>\n }\n\n <!-- Display Fields -->\n @if (vm.displayFields.length > 0) {\n <div class=\"card-fields\">\n @for (df of vm.displayFields; track df.field.name) {\n <div class=\"field-item\" [class.field-boolean]=\"df.field.type === 'boolean'\" [class.field-text]=\"df.field.type === 'text'\">\n @switch (df.field.type) {\n @case ('boolean') {\n <i class=\"field-icon\" [class.fa-solid]=\"true\"\n [class.fa-check]=\"df.booleanValue\"\n [class.fa-minus]=\"!df.booleanValue\"\n [class.bool-true]=\"df.booleanValue\"\n [class.bool-false]=\"!df.booleanValue\"></i>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @case ('number') {\n <span class=\"field-value\">{{ df.numericValue }}</span>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @case ('date') {\n <span class=\"field-value field-date\">{{ df.dateValue }}</span>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @default {\n <span class=\"field-label\">{{ df.field.label }}</span>\n <span class=\"field-text-value\" [innerHTML]=\"df.highlightedText\"></span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Card Footer with Badge -->\n @if (effectiveTemplate?.badgeField) {\n <div class=\"card-footer\">\n <mj-pill [value]=\"vm.badgeValue\"></mj-pill>\n </div>\n }\n\n <!-- Hidden field match indicator -->\n @if (vm.hasHiddenFieldMatch) {\n <div class=\"hidden-match-indicator\" [title]=\"'Matched in: ' + vm.hiddenMatchFieldName\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span>{{ vm.hiddenMatchFieldName }}</span>\n </div>\n }\n </div>\n }\n\n @if (effectiveRecords.length === 0) {\n <mj-empty-state\n class=\"no-results\"\n Variant=\"no-results\"\n Title=\"No records to display\" />\n }\n </div>\n }\n</div>\n", styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background: var(--mj-bg-surface);\n border-radius: 12px;\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\nmj-entity-cards .data-card:hover:not(.selected) {\n border-color: var(--mj-border-strong);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\nmj-entity-cards .data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\nmj-entity-cards .data-card.selected {\n border-color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .data-card.selected .card-title {\n color: var(--mj-brand-primary-hover);\n}\n\n/* Selected + hover state - same as regular hover */\nmj-entity-cards .data-card.selected:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\nmj-entity-cards .card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\nmj-entity-cards .card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n overflow: hidden;\n flex-shrink: 0;\n background: var(--mj-bg-surface-sunken);\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center;\n display: block;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\nmj-entity-cards .card-header-content {\n flex: 1;\n min-width: 0;\n}\n\nmj-entity-cards .card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\nmj-entity-cards .card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmj-entity-cards .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: var(--mj-text-muted);\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\nmj-entity-cards .card-open-btn:hover {\n background: var(--mj-bg-surface-card);\n color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .card-body {\n padding: 0 16px 16px 16px;\n}\n\nmj-entity-cards .card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Card Display Fields */\nmj-entity-cards .card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\nmj-entity-cards .field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\nmj-entity-cards .field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\nmj-entity-cards .field-item .field-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\nmj-entity-cards .field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-true {\n color: var(--mj-status-success);\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-false {\n color: var(--mj-text-muted);\n}\n\nmj-entity-cards .field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: var(--mj-text-secondary);\n}\n\n/* Text fields */\nmj-entity-cards .field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\nmj-entity-cards .field-item .field-text-value {\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\nmj-entity-cards .field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\nmj-entity-cards .card-footer {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* Highlight matches - scoped to work with innerHTML */\nmj-entity-cards .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\nmj-entity-cards .card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\nmj-entity-cards .hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: var(--mj-status-warning-bg);\n border-top: 1px solid var(--mj-status-warning-border);\n font-size: 11px;\n color: var(--mj-status-warning);\n}\n\nmj-entity-cards .hidden-match-indicator i {\n font-size: 10px;\n}\n\nmj-entity-cards .hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\nmj-entity-cards .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 \u2014 span the full grid width */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n}\n"] }]
|
|
1052
|
+
args: [{ standalone: false, selector: 'mj-entity-cards', encapsulation: ViewEncapsulation.None, 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 (vm of cardViewModels; track vm.trackId) {\n <div\n class=\"data-card\"\n [class.selected]=\"vm.isSelected\"\n (click)=\"onCardClick(vm.record)\">\n\n <!-- Card Header with Avatar/Icon -->\n <div class=\"card-header\">\n @switch (vm.thumbnailType) {\n @case ('image') {\n <div class=\"card-thumbnail\">\n <img [src]=\"vm.thumbnailUrl\" alt=\"\" />\n </div>\n }\n @case ('icon') {\n <div class=\"card-avatar\" [style.background-color]=\"vm.color\">\n <i [class]=\"vm.thumbnailUrl\"></i>\n </div>\n }\n @default {\n <div class=\"card-avatar\" [style.background-color]=\"vm.color\">\n {{ vm.initials }}\n </div>\n }\n }\n\n <div class=\"card-header-content\">\n <h3 class=\"card-title\" [innerHTML]=\"vm.highlightedTitle\"></h3>\n @if (effectiveTemplate?.subtitleField) {\n @if (subtitleIsPill) {\n <mj-pill [value]=\"vm.subtitleValue\"></mj-pill>\n } @else {\n <span class=\"card-subtitle\" [innerHTML]=\"vm.highlightedSubtitle\"></span>\n }\n }\n </div>\n\n <button class=\"card-open-btn\" (click)=\"onOpenClick($event, vm.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) {\n <p class=\"card-description\" [innerHTML]=\"vm.highlightedDescription\"></p>\n }\n\n <!-- Display Fields -->\n @if (vm.displayFields.length > 0) {\n <div class=\"card-fields\">\n @for (df of vm.displayFields; track df.field.name) {\n <div class=\"field-item\" [class.field-boolean]=\"df.field.type === 'boolean'\" [class.field-text]=\"df.field.type === 'text'\">\n @switch (df.field.type) {\n @case ('boolean') {\n <i class=\"field-icon\" [class.fa-solid]=\"true\"\n [class.fa-check]=\"df.booleanValue\"\n [class.fa-minus]=\"!df.booleanValue\"\n [class.bool-true]=\"df.booleanValue\"\n [class.bool-false]=\"!df.booleanValue\"></i>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @case ('number') {\n <span class=\"field-value\">{{ df.numericValue }}</span>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @case ('date') {\n <span class=\"field-value field-date\">{{ df.dateValue }}</span>\n <span class=\"field-label\">{{ df.field.label }}</span>\n }\n @default {\n <span class=\"field-label\">{{ df.field.label }}</span>\n <span class=\"field-text-value\" [innerHTML]=\"df.highlightedText\"></span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Card Footer with Badge -->\n @if (effectiveTemplate?.badgeField) {\n <div class=\"card-footer\">\n <mj-pill [value]=\"vm.badgeValue\"></mj-pill>\n </div>\n }\n\n <!-- Hidden field match indicator -->\n @if (vm.hasHiddenFieldMatch) {\n <div class=\"hidden-match-indicator\" [title]=\"'Matched in: ' + vm.hiddenMatchFieldName\">\n <i class=\"fa-solid fa-magnifying-glass\"></i>\n <span>{{ vm.hiddenMatchFieldName }}</span>\n </div>\n }\n </div>\n }\n\n @if (effectiveRecords.length === 0) {\n <mj-empty-state\n class=\"no-results\"\n Variant=\"no-results\"\n [Title]=\"entity ? 'No ' + entity.DisplayNamePlural + ' to display' : 'No records to display'\" />\n }\n </div>\n }\n</div>\n", styles: ["/* Scoped to mj-entity-cards to prevent style leakage with ViewEncapsulation.None */\nmj-entity-cards .cards-view-wrapper {\n height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\nmj-entity-cards .cards-container {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));\n gap: 20px;\n padding: 4px;\n}\n\nmj-entity-cards .data-card {\n background: var(--mj-bg-surface);\n border-radius: 12px;\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n/* Hover state for unselected cards */\nmj-entity-cards .data-card:hover:not(.selected) {\n border-color: var(--mj-border-strong);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\nmj-entity-cards .data-card:hover .card-open-btn {\n opacity: 1;\n}\n\n/* Selected state - subtle but clear */\nmj-entity-cards .data-card.selected {\n border-color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .data-card.selected .card-title {\n color: var(--mj-brand-primary-hover);\n}\n\n/* Selected + hover state - same as regular hover */\nmj-entity-cards .data-card.selected:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);\n transform: translateY(-2px);\n}\n\nmj-entity-cards .card-header {\n display: flex;\n align-items: flex-start;\n padding: 16px;\n gap: 12px;\n}\n\nmj-entity-cards .card-thumbnail {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n overflow: hidden;\n flex-shrink: 0;\n background: var(--mj-bg-surface-sunken);\n}\nmj-entity-cards .card-thumbnail img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center;\n display: block;\n}\n\nmj-entity-cards .card-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-inverse);\n flex-shrink: 0;\n}\n\nmj-entity-cards .card-header-content {\n flex: 1;\n min-width: 0;\n}\n\nmj-entity-cards .card-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n line-height: 1.3;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\nmj-entity-cards .card-subtitle {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\nmj-entity-cards .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: var(--mj-text-muted);\n opacity: 0;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\nmj-entity-cards .card-open-btn:hover {\n background: var(--mj-bg-surface-card);\n color: var(--mj-brand-primary);\n}\n\nmj-entity-cards .card-body {\n padding: 0 16px 16px 16px;\n}\n\nmj-entity-cards .card-description {\n margin: 0 0 12px 0;\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n/* Card Display Fields */\nmj-entity-cards .card-fields {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--mj-border-subtle);\n}\n\nmj-entity-cards .field-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 60px;\n}\n\nmj-entity-cards .field-item .field-value {\n font-size: 15px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\nmj-entity-cards .field-item .field-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Boolean fields */\nmj-entity-cards .field-item.field-boolean {\n flex-direction: row;\n align-items: center;\n gap: 6px;\n min-width: auto;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon {\n font-size: 14px;\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-true {\n color: var(--mj-status-success);\n}\n\nmj-entity-cards .field-item.field-boolean .field-icon.bool-false {\n color: var(--mj-text-muted);\n}\n\nmj-entity-cards .field-item.field-boolean .field-label {\n font-size: 12px;\n text-transform: none;\n color: var(--mj-text-secondary);\n}\n\n/* Text fields */\nmj-entity-cards .field-item.field-text {\n flex: 1 1 100%;\n max-width: 100%;\n}\n\nmj-entity-cards .field-item .field-text-value {\n font-size: 13px;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n word-break: break-word;\n}\n\n/* Date fields */\nmj-entity-cards .field-item .field-date {\n font-size: 13px;\n font-weight: 500;\n}\n\nmj-entity-cards .card-footer {\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n/* Highlight matches - scoped to work with innerHTML */\nmj-entity-cards .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n}\n\n/* Pill spacing when used as subtitle */\nmj-entity-cards .card-header-content mj-pill {\n display: block;\n margin-top: 4px;\n}\n\n/* Hidden field match indicator */\nmj-entity-cards .hidden-match-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n background: var(--mj-status-warning-bg);\n border-top: 1px solid var(--mj-status-warning-border);\n font-size: 11px;\n color: var(--mj-status-warning);\n}\n\nmj-entity-cards .hidden-match-indicator i {\n font-size: 10px;\n}\n\nmj-entity-cards .hidden-match-indicator span {\n font-weight: 500;\n}\n\n/* Loading state */\nmj-entity-cards .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 \u2014 span the full grid width */\nmj-entity-cards .no-results {\n grid-column: 1 / -1;\n}\n"] }]
|
|
917
1053
|
}], () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], { entity: [{
|
|
918
1054
|
type: Input
|
|
919
1055
|
}], records: [{
|
|
920
1056
|
type: Input
|
|
921
1057
|
}], selectedRecordId: [{
|
|
922
1058
|
type: Input
|
|
1059
|
+
}], CardTemplate: [{
|
|
1060
|
+
type: Input
|
|
923
1061
|
}], cardTemplate: [{
|
|
924
1062
|
type: Input
|
|
1063
|
+
}], HiddenFieldMatches: [{
|
|
1064
|
+
type: Input
|
|
925
1065
|
}], hiddenFieldMatches: [{
|
|
926
1066
|
type: Input
|
|
927
1067
|
}], filterText: [{
|