@memberjunction/ng-entity-viewer 6.2.0-edge.0 → 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-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 +67 -5
- package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.js +202 -71
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +159 -16
- 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 +446 -147
- 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 +245 -61
- 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 +6 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +28 -9
- 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/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 +17 -1
- 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 +39 -6
- 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 +70 -2
- 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 +155 -33
- 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.d.ts +8 -0
- package/dist/lib/view-types/view-type.contracts.d.ts.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 +243 -25
- package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.js +631 -180
- package/dist/lib/view-workspace/view-workspace.component.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
3
|
import { EntityFieldValueListType, RunView, CoerceImageSrc, IsDateOnlySQLType, FormatDateOnly } from '@memberjunction/core';
|
|
4
|
-
import {
|
|
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 '-';
|
|
@@ -612,53 +671,77 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
612
671
|
return String(value);
|
|
613
672
|
}
|
|
614
673
|
}
|
|
674
|
+
/** @deprecated Use {@link GetDateValue}. */
|
|
675
|
+
getDateValue(record, fieldName) {
|
|
676
|
+
return this.GetDateValue(record, fieldName);
|
|
677
|
+
}
|
|
615
678
|
isDateOnlyField(fieldName) {
|
|
616
679
|
return IsDateOnlySQLType(this.entity?.Fields.find(f => f.Name === fieldName)?.Type);
|
|
617
680
|
}
|
|
618
681
|
/**
|
|
619
682
|
* Get display label for a field using EntityFieldInfo's built-in DisplayNameOrName property
|
|
620
683
|
*/
|
|
621
|
-
|
|
684
|
+
GetFieldLabel(field) {
|
|
622
685
|
return field.DisplayNameOrName;
|
|
623
686
|
}
|
|
687
|
+
/** @deprecated Use {@link GetFieldLabel}. */
|
|
688
|
+
getFieldLabel(field) {
|
|
689
|
+
return this.GetFieldLabel(field);
|
|
690
|
+
}
|
|
624
691
|
// ========================================
|
|
625
692
|
// CARD DISPLAY HELPERS
|
|
626
693
|
// ========================================
|
|
627
|
-
|
|
694
|
+
GetRecordTrackId(record, index) {
|
|
628
695
|
if (!this.entity)
|
|
629
696
|
return `record_${index}`;
|
|
630
697
|
try {
|
|
631
|
-
const pk =
|
|
698
|
+
const pk = BuildPkString(record, this.entity);
|
|
632
699
|
if (pk && pk.trim().length > 0)
|
|
633
700
|
return pk;
|
|
634
701
|
}
|
|
635
702
|
catch { /* ignore */ }
|
|
636
703
|
return `record_${index}`;
|
|
637
704
|
}
|
|
638
|
-
|
|
705
|
+
/** @deprecated Use {@link GetRecordTrackId}. */
|
|
706
|
+
getRecordTrackId(record, index) {
|
|
707
|
+
return this.GetRecordTrackId(record, index);
|
|
708
|
+
}
|
|
709
|
+
IsSelected(record) {
|
|
639
710
|
if (!this.entity)
|
|
640
711
|
return false;
|
|
641
|
-
return
|
|
712
|
+
return BuildPkString(record, this.entity) === this.selectedRecordId;
|
|
642
713
|
}
|
|
643
|
-
|
|
714
|
+
/** @deprecated Use {@link IsSelected}. */
|
|
715
|
+
isSelected(record) {
|
|
716
|
+
return this.IsSelected(record);
|
|
717
|
+
}
|
|
718
|
+
OnCardClick(record) {
|
|
644
719
|
if (!this.entity)
|
|
645
720
|
return;
|
|
646
721
|
this.recordSelected.emit({
|
|
647
722
|
record,
|
|
648
723
|
entity: this.entity,
|
|
649
|
-
compositeKey:
|
|
724
|
+
compositeKey: BuildCompositeKey(record, this.entity)
|
|
650
725
|
});
|
|
651
726
|
}
|
|
652
|
-
|
|
727
|
+
/** @deprecated Use {@link OnCardClick}. */
|
|
728
|
+
onCardClick(record) {
|
|
729
|
+
return this.OnCardClick(record);
|
|
730
|
+
}
|
|
731
|
+
OnOpenClick(event, record) {
|
|
653
732
|
event.stopPropagation();
|
|
654
733
|
if (!this.entity)
|
|
655
734
|
return;
|
|
656
735
|
this.recordOpened.emit({
|
|
657
736
|
record,
|
|
658
737
|
entity: this.entity,
|
|
659
|
-
compositeKey:
|
|
738
|
+
compositeKey: BuildCompositeKey(record, this.entity)
|
|
660
739
|
});
|
|
661
740
|
}
|
|
741
|
+
/** @deprecated Use {@link OnOpenClick}. */
|
|
742
|
+
onOpenClick(event, record) {
|
|
743
|
+
return this.OnOpenClick(event, record);
|
|
744
|
+
}
|
|
662
745
|
// ========================================
|
|
663
746
|
// PRECOMPUTED VIEW-MODELS (perf)
|
|
664
747
|
// ========================================
|
|
@@ -669,8 +752,8 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
669
752
|
* template method bindings avoids re-running it on every Angular CD cycle.
|
|
670
753
|
*/
|
|
671
754
|
buildCardViewModels() {
|
|
672
|
-
const records = this.
|
|
673
|
-
this.
|
|
755
|
+
const records = this.EffectiveRecords;
|
|
756
|
+
this.CardViewModels = records.map((record, index) => this.buildCardViewModel(record, index));
|
|
674
757
|
}
|
|
675
758
|
/**
|
|
676
759
|
* Cheaply update the isSelected flag on the EXISTING card view-models in place,
|
|
@@ -681,7 +764,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
681
764
|
* compare per card with zero allocation or regex work.
|
|
682
765
|
*/
|
|
683
766
|
updateSelectionFlags() {
|
|
684
|
-
for (const vm of this.
|
|
767
|
+
for (const vm of this.CardViewModels) {
|
|
685
768
|
vm.isSelected = vm.pkString.length > 0 && vm.pkString === this.selectedRecordId;
|
|
686
769
|
}
|
|
687
770
|
}
|
|
@@ -692,9 +775,9 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
692
775
|
buildCardViewModel(record, index) {
|
|
693
776
|
const pkString = this.computePkString(record);
|
|
694
777
|
const trackId = pkString && pkString.trim().length > 0 ? pkString : `record_${index}`;
|
|
695
|
-
const template = this.
|
|
778
|
+
const template = this.EffectiveTemplate;
|
|
696
779
|
const subtitleField = template?.subtitleField ?? null;
|
|
697
|
-
const subtitleValue = subtitleField ? this.
|
|
780
|
+
const subtitleValue = subtitleField ? this.GetFieldValue(record, subtitleField) : '';
|
|
698
781
|
const descriptionField = template?.descriptionField ?? null;
|
|
699
782
|
return {
|
|
700
783
|
record,
|
|
@@ -702,17 +785,17 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
702
785
|
trackId,
|
|
703
786
|
isSelected: pkString.length > 0 && pkString === this.selectedRecordId,
|
|
704
787
|
color: this.computeRecordColor(pkString),
|
|
705
|
-
thumbnailType: this.
|
|
706
|
-
thumbnailUrl: this.
|
|
707
|
-
initials: this.
|
|
708
|
-
highlightedTitle: this.
|
|
788
|
+
thumbnailType: this.GetThumbnailType(record),
|
|
789
|
+
thumbnailUrl: this.GetThumbnailUrl(record),
|
|
790
|
+
initials: this.GetInitials(record),
|
|
791
|
+
highlightedTitle: this.HighlightMatch(this.GetCombinedTitle(record)),
|
|
709
792
|
subtitleValue,
|
|
710
|
-
highlightedSubtitle: this.
|
|
793
|
+
highlightedSubtitle: this.HighlightMatch(subtitleValue),
|
|
711
794
|
highlightedDescription: descriptionField
|
|
712
|
-
? this.
|
|
795
|
+
? this.HighlightMatch(this.GetTextValue(record, descriptionField, 100))
|
|
713
796
|
: '',
|
|
714
797
|
displayFields: this.buildDisplayFieldVMs(record, template),
|
|
715
|
-
badgeValue: template?.badgeField ? this.
|
|
798
|
+
badgeValue: template?.badgeField ? this.GetFieldValue(record, template.badgeField) : '',
|
|
716
799
|
hasHiddenFieldMatch: this.computeHasHiddenFieldMatch(pkString),
|
|
717
800
|
hiddenMatchFieldName: this.computeHiddenMatchFieldName(pkString)
|
|
718
801
|
};
|
|
@@ -725,10 +808,10 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
725
808
|
return [];
|
|
726
809
|
return template.displayFields.map(field => ({
|
|
727
810
|
field,
|
|
728
|
-
booleanValue: this.
|
|
729
|
-
numericValue: this.
|
|
730
|
-
dateValue: this.
|
|
731
|
-
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))
|
|
732
815
|
}));
|
|
733
816
|
}
|
|
734
817
|
/**
|
|
@@ -739,7 +822,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
739
822
|
if (!this.entity)
|
|
740
823
|
return '';
|
|
741
824
|
try {
|
|
742
|
-
return
|
|
825
|
+
return BuildPkString(record, this.entity);
|
|
743
826
|
}
|
|
744
827
|
catch {
|
|
745
828
|
return '';
|
|
@@ -764,7 +847,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
764
847
|
computeHasHiddenFieldMatch(pkString) {
|
|
765
848
|
if (!this.entity || !pkString)
|
|
766
849
|
return false;
|
|
767
|
-
return this.
|
|
850
|
+
return this.HiddenFieldMatches.has(pkString);
|
|
768
851
|
}
|
|
769
852
|
/**
|
|
770
853
|
* Resolve the display name of the hidden field that matched, from a PK string.
|
|
@@ -772,7 +855,7 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
772
855
|
computeHiddenMatchFieldName(pkString) {
|
|
773
856
|
if (!this.entity || !pkString)
|
|
774
857
|
return '';
|
|
775
|
-
const fieldName = this.
|
|
858
|
+
const fieldName = this.HiddenFieldMatches.get(pkString);
|
|
776
859
|
if (!fieldName)
|
|
777
860
|
return '';
|
|
778
861
|
const field = this.entity.Fields.find(f => f.Name === fieldName);
|
|
@@ -782,17 +865,21 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
782
865
|
* Get the combined title from all title fields for a record.
|
|
783
866
|
* Joins multiple IsNameField values with spaces (e.g., "Elizabeth Rodriguez").
|
|
784
867
|
*/
|
|
785
|
-
|
|
786
|
-
const template = this.
|
|
868
|
+
GetCombinedTitle(record) {
|
|
869
|
+
const template = this.EffectiveTemplate;
|
|
787
870
|
if (!template?.titleFields || template.titleFields.length === 0)
|
|
788
871
|
return '';
|
|
789
872
|
const parts = template.titleFields
|
|
790
|
-
.map(f => this.
|
|
873
|
+
.map(f => this.GetFieldValue(record, f))
|
|
791
874
|
.filter(v => v && v.trim().length > 0);
|
|
792
875
|
return parts.length > 0 ? parts.join(' ') : '';
|
|
793
876
|
}
|
|
794
|
-
|
|
795
|
-
|
|
877
|
+
/** @deprecated Use {@link GetCombinedTitle}. */
|
|
878
|
+
getCombinedTitle(record) {
|
|
879
|
+
return this.GetCombinedTitle(record);
|
|
880
|
+
}
|
|
881
|
+
GetInitials(record) {
|
|
882
|
+
const title = this.GetCombinedTitle(record);
|
|
796
883
|
if (!title)
|
|
797
884
|
return '?';
|
|
798
885
|
const words = title.split(/\s+/).filter(w => w.length > 0);
|
|
@@ -800,10 +887,14 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
800
887
|
return words[0].substring(0, 2).toUpperCase();
|
|
801
888
|
return (words[0][0] + words[words.length - 1][0]).toUpperCase();
|
|
802
889
|
}
|
|
890
|
+
/** @deprecated Use {@link GetInitials}. */
|
|
891
|
+
getInitials(record) {
|
|
892
|
+
return this.GetInitials(record);
|
|
893
|
+
}
|
|
803
894
|
/**
|
|
804
895
|
* Get the thumbnail type for a record, with per-record fallback through thumbnailFields
|
|
805
896
|
*/
|
|
806
|
-
|
|
897
|
+
GetThumbnailType(record) {
|
|
807
898
|
const fieldInfo = this.getEffectiveThumbnailField(record);
|
|
808
899
|
if (!fieldInfo)
|
|
809
900
|
return 'none';
|
|
@@ -819,26 +910,34 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
819
910
|
return 'icon';
|
|
820
911
|
return 'none';
|
|
821
912
|
}
|
|
913
|
+
/** @deprecated Use {@link GetThumbnailType}. */
|
|
914
|
+
getThumbnailType(record) {
|
|
915
|
+
return this.GetThumbnailType(record);
|
|
916
|
+
}
|
|
822
917
|
/**
|
|
823
918
|
* Get the thumbnail URL/value for a record, with per-record fallback
|
|
824
919
|
*/
|
|
825
|
-
|
|
920
|
+
GetThumbnailUrl(record) {
|
|
826
921
|
const fieldInfo = this.getEffectiveThumbnailField(record);
|
|
827
922
|
if (!fieldInfo)
|
|
828
923
|
return '';
|
|
829
924
|
return CoerceImageSrc(fieldInfo.value) || fieldInfo.value;
|
|
830
925
|
}
|
|
926
|
+
/** @deprecated Use {@link GetThumbnailUrl}. */
|
|
927
|
+
getThumbnailUrl(record) {
|
|
928
|
+
return this.GetThumbnailUrl(record);
|
|
929
|
+
}
|
|
831
930
|
/**
|
|
832
931
|
* Find the first thumbnail field that has a value for this record
|
|
833
932
|
* Returns both the field name and value for type determination
|
|
834
933
|
*/
|
|
835
934
|
getEffectiveThumbnailField(record) {
|
|
836
|
-
const template = this.
|
|
935
|
+
const template = this.EffectiveTemplate;
|
|
837
936
|
if (!template?.thumbnailFields || template.thumbnailFields.length === 0)
|
|
838
937
|
return null;
|
|
839
938
|
// Try each field in priority order until we find one with a value
|
|
840
939
|
for (const fieldName of template.thumbnailFields) {
|
|
841
|
-
const value = this.
|
|
940
|
+
const value = this.GetFieldValue(record, fieldName);
|
|
842
941
|
if (value && value.trim() !== '') {
|
|
843
942
|
return { fieldName, value };
|
|
844
943
|
}
|
|
@@ -862,10 +961,14 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
862
961
|
return true;
|
|
863
962
|
return false;
|
|
864
963
|
}
|
|
865
|
-
|
|
964
|
+
GetRecordColor(record) {
|
|
866
965
|
return this.computeRecordColor(this.computePkString(record));
|
|
867
966
|
}
|
|
868
|
-
|
|
967
|
+
/** @deprecated Use {@link GetRecordColor}. */
|
|
968
|
+
getRecordColor(record) {
|
|
969
|
+
return this.GetRecordColor(record);
|
|
970
|
+
}
|
|
971
|
+
IsEnumField(fieldName) {
|
|
869
972
|
if (!this.entity)
|
|
870
973
|
return false;
|
|
871
974
|
const field = this.entity.Fields.find(f => f.Name === fieldName);
|
|
@@ -873,45 +976,69 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
873
976
|
return false;
|
|
874
977
|
return field.ValueListTypeEnum !== EntityFieldValueListType.None && field.EntityFieldValues.length > 0;
|
|
875
978
|
}
|
|
876
|
-
|
|
877
|
-
|
|
979
|
+
/** @deprecated Use {@link IsEnumField}. */
|
|
980
|
+
isEnumField(fieldName) {
|
|
981
|
+
return this.IsEnumField(fieldName);
|
|
982
|
+
}
|
|
983
|
+
get SubtitleIsPill() {
|
|
984
|
+
const template = this.EffectiveTemplate;
|
|
878
985
|
if (!template?.subtitleField || !this.entity)
|
|
879
986
|
return false;
|
|
880
|
-
return this.
|
|
987
|
+
return this.IsEnumField(template.subtitleField);
|
|
881
988
|
}
|
|
882
|
-
|
|
989
|
+
/** @deprecated Use {@link SubtitleIsPill}. */
|
|
990
|
+
get subtitleIsPill() {
|
|
991
|
+
return this.SubtitleIsPill;
|
|
992
|
+
}
|
|
993
|
+
GetPillColorType(value) {
|
|
883
994
|
return PillColorUtil.getColorType(value);
|
|
884
995
|
}
|
|
996
|
+
/** @deprecated Use {@link GetPillColorType}. */
|
|
997
|
+
getPillColorType(value) {
|
|
998
|
+
return this.GetPillColorType(value);
|
|
999
|
+
}
|
|
885
1000
|
/**
|
|
886
1001
|
* Check if a record matched on a hidden field
|
|
887
1002
|
*/
|
|
888
|
-
|
|
1003
|
+
HasHiddenFieldMatch(record) {
|
|
889
1004
|
if (!this.entity)
|
|
890
1005
|
return false;
|
|
891
|
-
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);
|
|
892
1011
|
}
|
|
893
1012
|
/**
|
|
894
1013
|
* Get the display name of the hidden field that matched
|
|
895
1014
|
*/
|
|
896
|
-
|
|
1015
|
+
GetHiddenMatchFieldName(record) {
|
|
897
1016
|
if (!this.entity)
|
|
898
1017
|
return '';
|
|
899
|
-
const fieldName = this.
|
|
1018
|
+
const fieldName = this.HiddenFieldMatches.get(BuildPkString(record, this.entity));
|
|
900
1019
|
if (!fieldName || !this.entity)
|
|
901
1020
|
return '';
|
|
902
1021
|
// Look up the field in entity metadata and use DisplayNameOrName
|
|
903
1022
|
const field = this.entity.Fields.find(f => f.Name === fieldName);
|
|
904
1023
|
return field ? field.DisplayNameOrName : fieldName;
|
|
905
1024
|
}
|
|
1025
|
+
/** @deprecated Use {@link GetHiddenMatchFieldName}. */
|
|
1026
|
+
getHiddenMatchFieldName(record) {
|
|
1027
|
+
return this.GetHiddenMatchFieldName(record);
|
|
1028
|
+
}
|
|
906
1029
|
/**
|
|
907
1030
|
* Highlight matching text in a string based on the filter text
|
|
908
1031
|
* Uses HighlightUtil which only highlights if the text actually matches the pattern
|
|
909
1032
|
*/
|
|
910
|
-
|
|
1033
|
+
HighlightMatch(text) {
|
|
911
1034
|
return HighlightUtil.highlight(text, this.filterText, false);
|
|
912
1035
|
}
|
|
1036
|
+
/** @deprecated Use {@link HighlightMatch}. */
|
|
1037
|
+
highlightMatch(text) {
|
|
1038
|
+
return this.HighlightMatch(text);
|
|
1039
|
+
}
|
|
913
1040
|
static ɵfac = function EntityCardsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EntityCardsComponent)(i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
914
|
-
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) {
|
|
915
1042
|
i0.ɵɵelementStart(0, "div", 0);
|
|
916
1043
|
i0.ɵɵconditionalCreate(1, EntityCardsComponent_Conditional_1_Template, 2, 0, "div", 1)(2, EntityCardsComponent_Conditional_2_Template, 4, 1, "div", 2);
|
|
917
1044
|
i0.ɵɵelementEnd();
|
|
@@ -922,15 +1049,19 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
922
1049
|
}
|
|
923
1050
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityCardsComponent, [{
|
|
924
1051
|
type: Component,
|
|
925
|
-
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"] }]
|
|
926
1053
|
}], () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], { entity: [{
|
|
927
1054
|
type: Input
|
|
928
1055
|
}], records: [{
|
|
929
1056
|
type: Input
|
|
930
1057
|
}], selectedRecordId: [{
|
|
931
1058
|
type: Input
|
|
1059
|
+
}], CardTemplate: [{
|
|
1060
|
+
type: Input
|
|
932
1061
|
}], cardTemplate: [{
|
|
933
1062
|
type: Input
|
|
1063
|
+
}], HiddenFieldMatches: [{
|
|
1064
|
+
type: Input
|
|
934
1065
|
}], hiddenFieldMatches: [{
|
|
935
1066
|
type: Input
|
|
936
1067
|
}], filterText: [{
|