@memberjunction/ng-entity-viewer 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
2
2
  import { Subject } from 'rxjs';
3
3
  import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
4
- import { EntityFieldTSType, RunView } from '@memberjunction/core';
4
+ import { EntityFieldTSType, RunView, Metadata } from '@memberjunction/core';
5
5
  import { TimelineGroup } from '@memberjunction/ng-timeline';
6
6
  import { DEFAULT_VIEWER_CONFIG } from '../types';
7
7
  import { EntityDataGridComponent } from '../entity-data-grid/entity-data-grid.component';
@@ -165,7 +165,7 @@ function EntityViewerComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
165
165
  i0.ɵɵadvance();
166
166
  i0.ɵɵconditional(ctx_r1.effectiveConfig.showFilter ? 1 : -1);
167
167
  i0.ɵɵadvance();
168
- i0.ɵɵconditional(ctx_r1.effectiveConfig.showRecordCount && ctx_r1.entity ? 2 : -1);
168
+ i0.ɵɵconditional(ctx_r1.effectiveConfig.showRecordCount && ctx_r1.effectiveEntity ? 2 : -1);
169
169
  i0.ɵɵadvance();
170
170
  i0.ɵɵconditional(ctx_r1.effectiveConfig.showViewModeToggle ? 3 : -1);
171
171
  i0.ɵɵadvance();
@@ -216,20 +216,70 @@ function EntityViewerComponent_Conditional_18_Template(rf, ctx) { if (rf & 1) {
216
216
  export class EntityViewerComponent {
217
217
  cdr;
218
218
  // ========================================
219
- // INPUTS
219
+ // INPUTS (using getter/setter pattern)
220
220
  // ========================================
221
+ _entity = null;
222
+ _records = null;
223
+ _config = {};
224
+ _viewMode = null;
225
+ _filterText = null;
226
+ _sortState = null;
227
+ _viewEntity = null;
228
+ _timelineConfig = null;
229
+ _initialized = false;
221
230
  /**
222
231
  * The entity to display records for
223
232
  */
224
- entity = null;
233
+ get entity() {
234
+ return this._entity;
235
+ }
236
+ set entity(value) {
237
+ this._entity = value;
238
+ // Detect date fields for timeline support
239
+ this.detectDateFields();
240
+ if (this._initialized) {
241
+ if (value && !this._records) {
242
+ // Reset state for new entity - synchronously clear all data and force change detection
243
+ // before starting the async load to prevent stale data display
244
+ this.resetPaginationState();
245
+ this.cdr.detectChanges();
246
+ this.loadData();
247
+ }
248
+ else if (!value) {
249
+ this.internalRecords = [];
250
+ this.totalRecordCount = 0;
251
+ this.filteredRecordCount = 0;
252
+ this.resetPaginationState();
253
+ this.cdr.detectChanges();
254
+ }
255
+ }
256
+ }
225
257
  /**
226
258
  * Pre-loaded records (optional - if not provided, component loads data)
227
259
  */
228
- records = null;
260
+ get records() {
261
+ return this._records;
262
+ }
263
+ set records(value) {
264
+ this._records = value;
265
+ if (value) {
266
+ this.internalRecords = value;
267
+ this.totalRecordCount = value.length;
268
+ this.filteredRecordCount = value.length;
269
+ // Update timeline with new records
270
+ this.updateTimelineGroups();
271
+ }
272
+ }
229
273
  /**
230
274
  * Configuration options for the viewer
231
275
  */
232
- config = {};
276
+ get config() {
277
+ return this._config;
278
+ }
279
+ set config(value) {
280
+ this._config = value;
281
+ this.applyConfig();
282
+ }
233
283
  /**
234
284
  * Currently selected record ID (primary key string)
235
285
  */
@@ -238,16 +288,67 @@ export class EntityViewerComponent {
238
288
  * External view mode - allows parent to control view mode
239
289
  * Supports two-way binding: [(viewMode)]="state.viewMode"
240
290
  */
241
- viewMode = null;
291
+ get viewMode() {
292
+ return this._viewMode;
293
+ }
294
+ set viewMode(value) {
295
+ this._viewMode = value;
296
+ if (value !== null) {
297
+ this.internalViewMode = value;
298
+ }
299
+ }
242
300
  /**
243
301
  * External filter text - allows parent to control filter
244
302
  * Supports two-way binding: [(filterText)]="state.filterText"
245
303
  */
246
- filterText = null;
304
+ get filterText() {
305
+ return this._filterText;
306
+ }
307
+ set filterText(value) {
308
+ const oldFilter = this.debouncedFilterText;
309
+ this._filterText = value;
310
+ const newFilter = value ?? '';
311
+ this.internalFilterText = newFilter;
312
+ this.debouncedFilterText = newFilter;
313
+ if (this._initialized) {
314
+ // If server-side filtering and filter changed, reload from page 1
315
+ // Keep existing records visible during refresh for better UX
316
+ if (this.effectiveConfig.serverSideFiltering && newFilter !== oldFilter && !this._records) {
317
+ this.resetPaginationState(false);
318
+ this.loadData();
319
+ }
320
+ else {
321
+ this.updateFilteredCount();
322
+ }
323
+ this.cdr.detectChanges();
324
+ }
325
+ }
247
326
  /**
248
327
  * External sort state - allows parent to control sorting
249
328
  */
250
- sortState = null;
329
+ get sortState() {
330
+ return this._sortState;
331
+ }
332
+ set sortState(value) {
333
+ const oldSort = this.internalSortState;
334
+ this._sortState = value;
335
+ if (value !== null) {
336
+ this.internalSortState = value;
337
+ if (this._initialized) {
338
+ // If sort changed and using server-side sorting, reload
339
+ // Keep existing records visible during refresh for better UX
340
+ if (this.effectiveConfig.serverSideSorting && !this._records) {
341
+ const sortChanged = !oldSort || !value ||
342
+ oldSort.field !== value.field ||
343
+ oldSort.direction !== value.direction;
344
+ if (sortChanged) {
345
+ this.resetPaginationState(false);
346
+ this.loadData();
347
+ }
348
+ }
349
+ }
350
+ }
351
+ }
251
352
  /**
252
353
  * Custom grid column definitions
253
354
  */
@@ -261,7 +362,26 @@ export class EntityViewerComponent {
261
362
  * When provided, the component will use the view's WhereClause, GridState, SortState, etc.
262
363
  * The view's filter is additive - UserSearchString is applied ON TOP of the view's WhereClause
263
364
  */
264
- viewEntity = null;
365
+ get viewEntity() {
366
+ return this._viewEntity;
367
+ }
368
+ set viewEntity(value) {
369
+ this._viewEntity = value;
370
+ if (this._initialized && this._entity && !this._records) {
371
+ // Apply view's sort state if available
372
+ if (value) {
373
+ const viewSortInfo = value.ViewSortInfo;
374
+ if (viewSortInfo && viewSortInfo.length > 0) {
375
+ this.internalSortState = {
376
+ field: viewSortInfo[0].field,
377
+ direction: viewSortInfo[0].direction === 'Desc' ? 'desc' : 'asc'
378
+ };
379
+ }
380
+ }
381
+ this.resetPaginationState();
382
+ this.loadData();
383
+ }
384
+ }
265
385
  /**
266
386
  * Grid state configuration from a User View
267
387
  * Controls column visibility, widths, order, and sort settings
@@ -285,13 +405,12 @@ export class EntityViewerComponent {
285
405
  prev.segmentGrouping === value.segmentGrouping);
286
406
  if (!isEqual) {
287
407
  this._timelineConfig = value;
288
- if (value && this.entity) {
408
+ if (value && this._entity) {
289
409
  this.configureTimeline();
290
410
  this.cdr.markForCheck();
291
411
  }
292
412
  }
293
413
  }
294
- _timelineConfig = null;
295
414
  /**
296
415
  * Whether to show the grid toolbar.
297
416
  * When false, the grid is displayed without its own toolbar - useful when
@@ -463,6 +582,51 @@ export class EntityViewerComponent {
463
582
  // ========================================
464
583
  // COMPUTED PROPERTIES
465
584
  // ========================================
585
+ /**
586
+ * Get the effective entity - uses entity input if provided, otherwise derives from viewEntity
587
+ * This allows callers to provide just a viewEntity without explicitly setting the entity input.
588
+ * Uses fallback resolution when ViewEntityInfo is not available.
589
+ */
590
+ get effectiveEntity() {
591
+ if (this.entity) {
592
+ return this.entity;
593
+ }
594
+ // Auto-derive from viewEntity if available
595
+ if (this.viewEntity) {
596
+ return this.getEntityInfoFromViewEntity(this.viewEntity);
597
+ }
598
+ return null;
599
+ }
600
+ /**
601
+ * Gets EntityInfo from a ViewEntity with multiple fallback strategies.
602
+ * Priority: 1) ViewEntityInfo property (set by Load)
603
+ * 2) Entity name lookup (virtual field)
604
+ * 3) EntityID lookup
605
+ * Returns null if entity cannot be determined.
606
+ */
607
+ getEntityInfoFromViewEntity(viewEntity) {
608
+ // First try: ViewEntityInfo is the preferred source (set by UserViewEntityExtended.Load)
609
+ if (viewEntity.ViewEntityInfo) {
610
+ return viewEntity.ViewEntityInfo;
611
+ }
612
+ const md = new Metadata();
613
+ // Second try: Look up by Entity name (virtual field that returns entity name)
614
+ if (viewEntity.Entity) {
615
+ const entityByName = md.Entities.find(e => e.Name === viewEntity.Entity);
616
+ if (entityByName) {
617
+ return entityByName;
618
+ }
619
+ }
620
+ // Third try: Look up by EntityID
621
+ if (viewEntity.EntityID) {
622
+ const entityById = md.Entities.find(e => e.ID === viewEntity.EntityID);
623
+ if (entityById) {
624
+ return entityById;
625
+ }
626
+ }
627
+ console.warn(`[EntityViewer] Could not determine entity for view "${viewEntity.Name}" (ID: ${viewEntity.ID})`);
628
+ return null;
629
+ }
466
630
  /**
467
631
  * Get the effective view mode (external or internal)
468
632
  */
@@ -524,17 +688,18 @@ export class EntityViewerComponent {
524
688
  * which would cause the grid to reinitialize
525
689
  */
526
690
  get gridParams() {
527
- if (!this.entity) {
691
+ const entity = this.effectiveEntity;
692
+ if (!entity) {
528
693
  return null;
529
694
  }
530
695
  // Check if we need to recreate the params object
531
- const entityChanged = this._lastGridParamsEntity !== this.entity.Name;
696
+ const entityChanged = this._lastGridParamsEntity !== entity.Name;
532
697
  const viewEntityChanged = this._lastGridParamsViewEntity !== this.viewEntity;
533
698
  if (entityChanged || viewEntityChanged || !this._cachedGridParams) {
534
- this._lastGridParamsEntity = this.entity.Name;
699
+ this._lastGridParamsEntity = entity.Name;
535
700
  this._lastGridParamsViewEntity = this.viewEntity ?? null;
536
701
  this._cachedGridParams = {
537
- EntityName: this.entity.Name,
702
+ EntityName: entity.Name,
538
703
  ViewEntity: this.viewEntity || undefined
539
704
  };
540
705
  }
@@ -701,91 +866,11 @@ export class EntityViewerComponent {
701
866
  direction: this.effectiveConfig.defaultSortDirection ?? 'asc'
702
867
  };
703
868
  }
704
- // Note: We don't call loadData() here because ngOnChanges runs before ngOnInit
705
- // and already handles the initial entity binding. Calling loadData() here would
706
- // result in duplicate data loading (200 records instead of 100).
707
- }
708
- ngOnChanges(changes) {
709
- if (changes['config']) {
710
- this.applyConfig();
711
- }
712
- if (changes['entity']) {
713
- // Detect date fields for timeline support
714
- this.detectDateFields();
715
- if (this.entity && !this.records) {
716
- // Reset state for new entity - synchronously clear all data and force change detection
717
- // before starting the async load to prevent stale data display
718
- this.resetPaginationState();
719
- this.cdr.detectChanges();
720
- this.loadData();
721
- }
722
- else if (!this.entity) {
723
- this.internalRecords = [];
724
- this.totalRecordCount = 0;
725
- this.filteredRecordCount = 0;
726
- this.resetPaginationState();
727
- this.cdr.detectChanges();
728
- }
729
- }
730
- if (changes['records'] && this.records) {
731
- this.internalRecords = this.records;
732
- this.totalRecordCount = this.records.length;
733
- this.filteredRecordCount = this.records.length;
734
- // Update timeline with new records
735
- this.updateTimelineGroups();
736
- }
737
- // Timeline config is now handled by setter - no ngOnChanges handling needed
738
- // Handle external filter text changes (from parent component)
739
- if (changes['filterText']) {
740
- const newFilter = this.filterText ?? '';
741
- const oldFilter = this.debouncedFilterText;
742
- this.internalFilterText = newFilter;
743
- this.debouncedFilterText = newFilter;
744
- // If server-side filtering and filter changed, reload from page 1
745
- if (this.effectiveConfig.serverSideFiltering && newFilter !== oldFilter && !this.records) {
746
- this.resetPaginationState();
747
- this.loadData();
748
- }
749
- else {
750
- this.updateFilteredCount();
751
- }
752
- this.cdr.detectChanges();
753
- }
754
- // Handle external view mode changes
755
- if (changes['viewMode'] && this.viewMode !== null) {
756
- this.internalViewMode = this.viewMode;
757
- }
758
- // Handle external sort state changes
759
- if (changes['sortState'] && this.sortState !== null) {
760
- const oldSort = this.internalSortState;
761
- this.internalSortState = this.sortState;
762
- // If sort changed and using server-side sorting, reload
763
- if (this.effectiveConfig.serverSideSorting && !this.records) {
764
- const sortChanged = !oldSort || !this.sortState ||
765
- oldSort.field !== this.sortState.field ||
766
- oldSort.direction !== this.sortState.direction;
767
- if (sortChanged) {
768
- this.resetPaginationState();
769
- this.loadData();
770
- }
771
- }
772
- }
773
- // Handle viewEntity changes - reload data when view changes
774
- if (changes['viewEntity']) {
775
- if (this.entity && !this.records) {
776
- // Apply view's sort state if available
777
- if (this.viewEntity) {
778
- const viewSortInfo = this.viewEntity.ViewSortInfo;
779
- if (viewSortInfo && viewSortInfo.length > 0) {
780
- this.internalSortState = {
781
- field: viewSortInfo[0].field,
782
- direction: viewSortInfo[0].direction === 'Desc' ? 'desc' : 'asc'
783
- };
784
- }
785
- }
786
- this.resetPaginationState();
787
- this.loadData();
788
- }
869
+ // Mark as initialized - setters will now trigger data loading
870
+ this._initialized = true;
871
+ // If entity was set before initialization, load data now
872
+ if (this._entity && !this._records) {
873
+ this.loadData();
789
874
  }
790
875
  }
791
876
  ngOnDestroy() {
@@ -810,8 +895,9 @@ export class EntityViewerComponent {
810
895
  this.debouncedFilterText = filterText;
811
896
  this.filterTextChange.emit(filterText);
812
897
  // If server-side filtering and filter changed, reload from page 1
898
+ // Keep existing records visible during refresh for better UX
813
899
  if (this.effectiveConfig.serverSideFiltering && filterText !== oldFilter && !this.records) {
814
- this.resetPaginationState();
900
+ this.resetPaginationState(false);
815
901
  this.loadData();
816
902
  }
817
903
  else {
@@ -835,42 +921,54 @@ export class EntityViewerComponent {
835
921
  }
836
922
  /**
837
923
  * Reset pagination state for a fresh load.
838
- * Clears all record data and counts to prevent stale data display during entity switches.
924
+ * When clearRecords is true (default), clears all record data - use for entity switches.
925
+ * When clearRecords is false, keeps existing records visible during refresh - use for sort/filter changes.
839
926
  */
840
- resetPaginationState() {
927
+ resetPaginationState(clearRecords = true) {
841
928
  this.pagination = {
842
929
  currentPage: 0,
843
930
  pageSize: this.effectiveConfig.pageSize,
844
- totalRecords: 0,
931
+ totalRecords: clearRecords ? 0 : this.pagination.totalRecords,
845
932
  hasMore: false,
846
933
  isLoading: false
847
934
  };
848
- this.internalRecords = [];
849
- this.totalRecordCount = 0;
850
- this.filteredRecordCount = 0;
935
+ if (clearRecords) {
936
+ this.internalRecords = [];
937
+ this.totalRecordCount = 0;
938
+ this.filteredRecordCount = 0;
939
+ }
851
940
  this.isInitialLoad = true;
852
941
  }
853
942
  // ========================================
854
943
  // DATA LOADING
855
944
  // ========================================
945
+ // Sequence counter for tracking load requests and detecting stale responses
946
+ _loadSequence = 0;
947
+ // Flag to indicate a reload is pending (requested while another load was in progress)
948
+ _pendingReload = false;
856
949
  /**
857
950
  * Load data for the current entity with server-side filtering/sorting/pagination
858
951
  */
859
952
  async loadData() {
860
- if (!this.entity) {
953
+ const entity = this.effectiveEntity;
954
+ if (!entity) {
861
955
  this.internalRecords = [];
862
956
  this.totalRecordCount = 0;
863
957
  this.filteredRecordCount = 0;
864
958
  return;
865
959
  }
866
- // Prevent concurrent loads which can cause duplicate records
960
+ // Increment sequence to track this load request
961
+ const loadId = ++this._loadSequence;
962
+ // If a load is already in progress, mark that we need to reload when it completes
963
+ // This handles the case where view/filter changes occur during an active load
867
964
  if (this.isLoading) {
965
+ this._pendingReload = true;
868
966
  return;
869
967
  }
870
968
  this.isLoading = true;
871
969
  this.pagination.isLoading = true;
872
970
  this.loadingMessage = this.isInitialLoad
873
- ? `Loading ${this.entity.Name}...`
971
+ ? `Loading ${entity.Name}...`
874
972
  : 'Loading more records...';
875
973
  this.cdr.detectChanges();
876
974
  const startTime = Date.now();
@@ -893,14 +991,22 @@ export class EntityViewerComponent {
893
991
  // The view's WhereClause is the "business filter" - UserSearchString is additive
894
992
  const extraFilter = this.viewEntity?.WhereClause || undefined;
895
993
  const result = await rv.RunView({
896
- EntityName: this.entity.Name,
994
+ EntityName: entity.Name,
897
995
  ResultType: 'entity_object',
898
996
  MaxRows: config.pageSize,
899
997
  StartRow: startRow,
900
998
  OrderBy: orderBy,
901
999
  ExtraFilter: extraFilter,
902
- UserSearchString: config.serverSideFiltering ? this.debouncedFilterText || undefined : undefined
1000
+ // Only use UserSearchString for regular text search, NOT for smart filters
1001
+ // Smart filters generate WhereClause via AI on the server, so the prompt text should not be passed as UserSearchString
1002
+ UserSearchString: config.serverSideFiltering && !this.viewEntity?.SmartFilterEnabled
1003
+ ? this.debouncedFilterText || undefined
1004
+ : undefined
903
1005
  });
1006
+ // Check if this load is still the current one (detect stale responses)
1007
+ if (loadId !== this._loadSequence) {
1008
+ return;
1009
+ }
904
1010
  if (result.Success) {
905
1011
  // Append or replace records based on whether this is initial load
906
1012
  if (this.isInitialLoad) {
@@ -928,7 +1034,6 @@ export class EntityViewerComponent {
928
1034
  this.updateTimelineGroups();
929
1035
  }
930
1036
  else {
931
- console.error('Failed to load records:', result.ErrorMessage);
932
1037
  if (this.isInitialLoad) {
933
1038
  this.internalRecords = [];
934
1039
  }
@@ -937,7 +1042,6 @@ export class EntityViewerComponent {
937
1042
  }
938
1043
  }
939
1044
  catch (error) {
940
- console.error('Error loading records:', error);
941
1045
  if (this.isInitialLoad) {
942
1046
  this.internalRecords = [];
943
1047
  }
@@ -949,6 +1053,13 @@ export class EntityViewerComponent {
949
1053
  this.pagination.isLoading = false;
950
1054
  this.isInitialLoad = false;
951
1055
  this.cdr.detectChanges();
1056
+ // If a reload was requested while we were loading, trigger it now
1057
+ if (this._pendingReload) {
1058
+ this._pendingReload = false;
1059
+ this.resetPaginationState();
1060
+ // Use setTimeout to break the call stack and allow Angular to process
1061
+ setTimeout(() => this.loadData(), 0);
1062
+ }
952
1063
  }
953
1064
  }
954
1065
  /**
@@ -963,10 +1074,11 @@ export class EntityViewerComponent {
963
1074
  }
964
1075
  /**
965
1076
  * Refresh data (re-load from server, starting at page 1)
1077
+ * Keeps existing records visible during refresh for better UX
966
1078
  */
967
1079
  refresh() {
968
1080
  if (!this.records) {
969
- this.resetPaginationState();
1081
+ this.resetPaginationState(false);
970
1082
  this.loadData();
971
1083
  }
972
1084
  }
@@ -1012,12 +1124,13 @@ export class EntityViewerComponent {
1012
1124
  this.internalSortState = event.sort;
1013
1125
  this.sortChanged.emit(event);
1014
1126
  // If server-side sorting, reload from page 1
1127
+ // Keep existing records visible during refresh for better UX
1015
1128
  if (this.effectiveConfig.serverSideSorting && !this.records) {
1016
1129
  const sortChanged = !oldSort || !event.sort ||
1017
1130
  oldSort.field !== event.sort?.field ||
1018
1131
  oldSort.direction !== event.sort?.direction;
1019
1132
  if (sortChanged) {
1020
- this.resetPaginationState();
1133
+ this.resetPaginationState(false);
1021
1134
  this.loadData();
1022
1135
  }
1023
1136
  }
@@ -1057,11 +1170,12 @@ export class EntityViewerComponent {
1057
1170
  * Maps to recordSelected event for parent components
1058
1171
  */
1059
1172
  onDataGridRowClick(event) {
1060
- if (!this.entity || !event.row)
1173
+ const entity = this.effectiveEntity;
1174
+ if (!entity || !event.row)
1061
1175
  return;
1062
1176
  this.recordSelected.emit({
1063
1177
  record: event.row,
1064
- entity: this.entity,
1178
+ entity: entity,
1065
1179
  compositeKey: event.row.PrimaryKey
1066
1180
  });
1067
1181
  }
@@ -1070,11 +1184,12 @@ export class EntityViewerComponent {
1070
1184
  * Maps to recordOpened event for parent components
1071
1185
  */
1072
1186
  onDataGridRowDoubleClick(event) {
1073
- if (!this.entity || !event.row)
1187
+ const entity = this.effectiveEntity;
1188
+ if (!entity || !event.row)
1074
1189
  return;
1075
1190
  this.recordOpened.emit({
1076
1191
  record: event.row,
1077
- entity: this.entity,
1192
+ entity: entity,
1078
1193
  compositeKey: event.row.PrimaryKey
1079
1194
  });
1080
1195
  }
@@ -1093,8 +1208,9 @@ export class EntityViewerComponent {
1093
1208
  this.internalSortState = newSort;
1094
1209
  this.sortChanged.emit({ sort: newSort });
1095
1210
  // If server-side sorting, reload from page 1
1211
+ // Keep existing records visible during refresh for better UX
1096
1212
  if (this.effectiveConfig.serverSideSorting && !this.records) {
1097
- this.resetPaginationState();
1213
+ this.resetPaginationState(false);
1098
1214
  this.loadData();
1099
1215
  }
1100
1216
  }
@@ -1153,10 +1269,11 @@ export class EntityViewerComponent {
1153
1269
  */
1154
1270
  onTimelineEventClick(event) {
1155
1271
  const record = event.event.entity;
1156
- if (record && this.entity) {
1272
+ const entity = this.effectiveEntity;
1273
+ if (record && entity) {
1157
1274
  this.recordSelected.emit({
1158
1275
  record,
1159
- entity: this.entity,
1276
+ entity: entity,
1160
1277
  compositeKey: record.PrimaryKey
1161
1278
  });
1162
1279
  }
@@ -1395,7 +1512,7 @@ export class EntityViewerComponent {
1395
1512
  } if (rf & 2) {
1396
1513
  let _t;
1397
1514
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.dataGridRef = _t.first);
1398
- } }, hostAttrs: [2, "display", "block", "height", "100%"], inputs: { entity: "entity", records: "records", config: "config", selectedRecordId: "selectedRecordId", viewMode: "viewMode", filterText: "filterText", sortState: "sortState", gridColumns: "gridColumns", cardTemplate: "cardTemplate", viewEntity: "viewEntity", gridState: "gridState", timelineConfig: "timelineConfig", showGridToolbar: "showGridToolbar", gridToolbarConfig: "gridToolbarConfig", gridSelectionMode: "gridSelectionMode", showAddToListButton: "showAddToListButton" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened", dataLoaded: "dataLoaded", viewModeChange: "viewModeChange", filterTextChange: "filterTextChange", filteredCountChanged: "filteredCountChanged", sortChanged: "sortChanged", gridStateChanged: "gridStateChanged", timelineConfigChange: "timelineConfigChange", addRequested: "addRequested", deleteRequested: "deleteRequested", refreshRequested: "refreshRequested", exportRequested: "exportRequested", addToListRequested: "addToListRequested", selectionChanged: "selectionChanged" }, features: [i0.ɵɵNgOnChangesFeature], decls: 19, vars: 38, consts: [[1, "entity-viewer-container"], [1, "viewer-header"], [1, "viewer-content"], [1, "loading-container", 3, "hidden"], ["size", "medium", 3, "text"], [1, "loading-overlay", 3, "hidden"], ["size", "small", 3, "text"], [1, "empty-state", 3, "hidden"], [1, "fa-solid", "fa-database"], [1, "fa-solid", "fa-inbox"], [3, "AfterRowClick", "AfterRowDoubleClick", "AfterSort", "GridStateChanged", "SelectionChange", "NewButtonClick", "RefreshButtonClick", "DeleteButtonClick", "ExportButtonClick", "AddToListRequested", "hidden", "Data", "Params", "FilterText", "GridState", "Height", "ShowToolbar", "ToolbarConfig", "SelectionMode", "ShowAddToListButton", "AllowLoad"], [3, "recordSelected", "recordOpened", "hidden", "entity", "records", "selectedRecordId", "cardTemplate", "hiddenFieldMatches", "filterText"], [3, "afterEventClick", "hidden", "groups", "orientation", "layout", "sortOrder", "segmentGrouping", "segmentsCollapsible", "segmentsDefaultExpanded", "selectedEventId"], [3, "pagination", "loadedRecordCount"], [1, "filter-container"], [1, "record-count"], [1, "view-mode-toggle"], [1, "fa-solid", "fa-search", "filter-icon"], ["type", "text", 1, "filter-input", 3, "input", "placeholder", "value"], ["title", "Clear filter", 1, "clear-filter-btn"], ["title", "Clear filter", 1, "clear-filter-btn", 3, "click"], [1, "fa-solid", "fa-times"], ["title", "Grid View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-list"], ["title", "Cards View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-grip"], ["title", "Timeline View", 1, "toggle-btn", 3, "active"], ["title", "Timeline View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-timeline"], [1, "timeline-date-selector"], [1, "fa-solid", "fa-calendar-days"], [1, "date-field-label"], [1, "date-field-select", 3, "value"], [1, "timeline-orientation-toggle"], [1, "toggle-btn", 3, "click", "title"], [1, "timeline-sort-toggle"], [1, "date-field-select", 3, "change", "value"], [3, "value"], [3, "loadMore", "pagination", "loadedRecordCount"]], template: function EntityViewerComponent_Template(rf, ctx) { if (rf & 1) {
1515
+ } }, hostAttrs: [2, "display", "block", "height", "100%"], inputs: { entity: "entity", records: "records", config: "config", selectedRecordId: "selectedRecordId", viewMode: "viewMode", filterText: "filterText", sortState: "sortState", gridColumns: "gridColumns", cardTemplate: "cardTemplate", viewEntity: "viewEntity", gridState: "gridState", timelineConfig: "timelineConfig", showGridToolbar: "showGridToolbar", gridToolbarConfig: "gridToolbarConfig", gridSelectionMode: "gridSelectionMode", showAddToListButton: "showAddToListButton" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened", dataLoaded: "dataLoaded", viewModeChange: "viewModeChange", filterTextChange: "filterTextChange", filteredCountChanged: "filteredCountChanged", sortChanged: "sortChanged", gridStateChanged: "gridStateChanged", timelineConfigChange: "timelineConfigChange", addRequested: "addRequested", deleteRequested: "deleteRequested", refreshRequested: "refreshRequested", exportRequested: "exportRequested", addToListRequested: "addToListRequested", selectionChanged: "selectionChanged" }, decls: 19, vars: 38, consts: [[1, "entity-viewer-container"], [1, "viewer-header"], [1, "viewer-content"], [1, "loading-container", 3, "hidden"], ["size", "medium", 3, "text"], [1, "loading-overlay", 3, "hidden"], ["size", "small", 3, "text"], [1, "empty-state", 3, "hidden"], [1, "fa-solid", "fa-database"], [1, "fa-solid", "fa-inbox"], [3, "AfterRowClick", "AfterRowDoubleClick", "AfterSort", "GridStateChanged", "SelectionChange", "NewButtonClick", "RefreshButtonClick", "DeleteButtonClick", "ExportButtonClick", "AddToListRequested", "hidden", "Data", "Params", "FilterText", "GridState", "Height", "ShowToolbar", "ToolbarConfig", "SelectionMode", "ShowAddToListButton", "AllowLoad"], [3, "recordSelected", "recordOpened", "hidden", "entity", "records", "selectedRecordId", "cardTemplate", "hiddenFieldMatches", "filterText"], [3, "afterEventClick", "hidden", "groups", "orientation", "layout", "sortOrder", "segmentGrouping", "segmentsCollapsible", "segmentsDefaultExpanded", "selectedEventId"], [3, "pagination", "loadedRecordCount"], [1, "filter-container"], [1, "record-count"], [1, "view-mode-toggle"], [1, "fa-solid", "fa-search", "filter-icon"], ["type", "text", 1, "filter-input", 3, "input", "placeholder", "value"], ["title", "Clear filter", 1, "clear-filter-btn"], ["title", "Clear filter", 1, "clear-filter-btn", 3, "click"], [1, "fa-solid", "fa-times"], ["title", "Grid View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-list"], ["title", "Cards View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-grip"], ["title", "Timeline View", 1, "toggle-btn", 3, "active"], ["title", "Timeline View", 1, "toggle-btn", 3, "click"], [1, "fa-solid", "fa-timeline"], [1, "timeline-date-selector"], [1, "fa-solid", "fa-calendar-days"], [1, "date-field-label"], [1, "date-field-select", 3, "value"], [1, "timeline-orientation-toggle"], [1, "toggle-btn", 3, "click", "title"], [1, "timeline-sort-toggle"], [1, "date-field-select", 3, "change", "value"], [3, "value"], [3, "loadMore", "pagination", "loadedRecordCount"]], template: function EntityViewerComponent_Template(rf, ctx) { if (rf & 1) {
1399
1516
  i0.ɵɵelementStart(0, "div", 0);
1400
1517
  i0.ɵɵtemplate(1, EntityViewerComponent_Conditional_1_Template, 5, 4, "div", 1);
1401
1518
  i0.ɵɵelementStart(2, "div", 2)(3, "div", 3);
@@ -1438,26 +1555,26 @@ export class EntityViewerComponent {
1438
1555
  i0.ɵɵadvance();
1439
1556
  i0.ɵɵproperty("text", ctx.loadingMessage);
1440
1557
  i0.ɵɵadvance();
1441
- i0.ɵɵproperty("hidden", !!ctx.entity);
1558
+ i0.ɵɵproperty("hidden", !!ctx.effectiveEntity);
1442
1559
  i0.ɵɵadvance(4);
1443
- i0.ɵɵproperty("hidden", !ctx.entity || ctx.filteredRecords.length > 0 || ctx.isLoading);
1560
+ i0.ɵɵproperty("hidden", !ctx.effectiveEntity || ctx.filteredRecords.length > 0 || ctx.isLoading);
1444
1561
  i0.ɵɵadvance(3);
1445
1562
  i0.ɵɵtextInterpolate(ctx.debouncedFilterText ? "No matching records" : "No records found");
1446
1563
  i0.ɵɵadvance();
1447
- i0.ɵɵproperty("hidden", ctx.effectiveViewMode !== "grid" || !ctx.entity)("Data", ctx.filteredRecords)("Params", ctx.gridParams)("FilterText", ctx.debouncedFilterText)("GridState", ctx.gridState)("Height", "auto")("ShowToolbar", ctx.showGridToolbar)("ToolbarConfig", ctx.effectiveGridToolbarConfig)("SelectionMode", ctx.gridSelectionMode)("ShowAddToListButton", ctx.showAddToListButton)("AllowLoad", false);
1564
+ i0.ɵɵproperty("hidden", ctx.effectiveViewMode !== "grid" || !ctx.effectiveEntity)("Data", ctx.filteredRecords)("Params", ctx.gridParams)("FilterText", ctx.debouncedFilterText)("GridState", ctx.gridState)("Height", "auto")("ShowToolbar", ctx.showGridToolbar)("ToolbarConfig", ctx.effectiveGridToolbarConfig)("SelectionMode", ctx.gridSelectionMode)("ShowAddToListButton", ctx.showAddToListButton)("AllowLoad", false);
1448
1565
  i0.ɵɵadvance();
1449
- i0.ɵɵproperty("hidden", ctx.effectiveViewMode !== "cards" || !ctx.entity)("entity", ctx.entity)("records", ctx.filteredRecords)("selectedRecordId", ctx.selectedRecordId)("cardTemplate", ctx.cardTemplate)("hiddenFieldMatches", ctx.hiddenFieldMatches)("filterText", ctx.debouncedFilterText);
1566
+ i0.ɵɵproperty("hidden", ctx.effectiveViewMode !== "cards" || !ctx.effectiveEntity)("entity", ctx.effectiveEntity)("records", ctx.filteredRecords)("selectedRecordId", ctx.selectedRecordId)("cardTemplate", ctx.cardTemplate)("hiddenFieldMatches", ctx.hiddenFieldMatches)("filterText", ctx.debouncedFilterText);
1450
1567
  i0.ɵɵadvance();
1451
1568
  i0.ɵɵproperty("hidden", ctx.effectiveViewMode !== "timeline" || !ctx.hasDateFields)("groups", ctx.timelineGroups)("orientation", ctx.timelineOrientation)("layout", ctx.timelineOrientation === "vertical" ? "alternating" : "single")("sortOrder", ctx.timelineSortOrder)("segmentGrouping", ctx.timelineSegmentGrouping)("segmentsCollapsible", true)("segmentsDefaultExpanded", true)("selectedEventId", ctx.timelineSelectedEventId);
1452
1569
  i0.ɵɵadvance();
1453
- i0.ɵɵconditional(ctx.effectiveConfig.showPagination && ctx.entity && (ctx.pagination.hasMore || ctx.pagination.totalRecords > ctx.effectiveConfig.pageSize) ? 18 : -1);
1454
- } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i2.LoadingComponent, i3.TimelineComponent, i4.EntityCardsComponent, i5.PaginationComponent, i6.EntityDataGridComponent, i7.DecimalPipe], styles: [".entity-viewer-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: #fafafa;\n}\n\n\n\nmj-pagination[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n.viewer-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 12px 16px;\n background: white;\n border-bottom: 1px solid #e0e0e0;\n flex-shrink: 0;\n}\n\n\n\n.filter-container[_ngcontent-%COMP%] {\n flex: 1;\n max-width: 400px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.filter-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 12px;\n color: #9e9e9e;\n font-size: 14px;\n pointer-events: none;\n}\n\n.filter-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 8px 36px 8px 36px;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n font-size: 14px;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.filter-input[_ngcontent-%COMP%]:focus {\n border-color: #1976d2;\n}\n\n.filter-input[_ngcontent-%COMP%]::placeholder {\n color: #9e9e9e;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%] {\n position: absolute;\n right: 8px;\n width: 20px;\n height: 20px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9e9e9e;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n color: #616161;\n}\n\n\n\n.record-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: #757575;\n white-space: nowrap;\n}\n\n\n\n.view-mode-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n.toggle-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #757575;\n transition: all 0.15s ease;\n}\n\n.toggle-btn[_ngcontent-%COMP%]:hover {\n color: #424242;\n}\n\n.toggle-btn.active[_ngcontent-%COMP%] {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n\n\n.timeline-date-selector[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: #616161;\n}\n\n.timeline-date-selector[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #9e9e9e;\n}\n\n.date-field-label[_ngcontent-%COMP%] {\n color: #424242;\n font-weight: 500;\n}\n\n.date-field-select[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 13px;\n background: white;\n color: #424242;\n cursor: pointer;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.date-field-select[_ngcontent-%COMP%]:hover {\n border-color: #bdbdbd;\n}\n\n.date-field-select[_ngcontent-%COMP%]:focus {\n border-color: #1976d2;\n}\n\n\n\n.timeline-orientation-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n\n\n.viewer-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n position: relative;\n background: white;\n}\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n}\n\n\n\n.loading-overlay[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(255, 255, 255, 0.8);\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #9e9e9e;\n text-align: center;\n}\n\n.empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n}\n\n\n\nmj-entity-cards[hidden][_ngcontent-%COMP%], \nmj-timeline[hidden][_ngcontent-%COMP%] {\n display: none !important;\n}\n\n\n\nmj-entity-cards[_ngcontent-%COMP%]:not([hidden]), \nmj-timeline[_ngcontent-%COMP%]:not([hidden]) {\n display: block;\n height: 100%;\n width: 100%;\n}"] });
1570
+ i0.ɵɵconditional(ctx.effectiveConfig.showPagination && ctx.effectiveEntity && (ctx.pagination.hasMore || ctx.pagination.totalRecords > ctx.effectiveConfig.pageSize) ? 18 : -1);
1571
+ } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i2.LoadingComponent, i3.TimelineComponent, i4.EntityCardsComponent, i5.PaginationComponent, i6.EntityDataGridComponent, i7.DecimalPipe], styles: [".entity-viewer-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: #fafafa;\n}\n\n\n\nmj-pagination[_ngcontent-%COMP%] {\n flex-shrink: 0;\n}\n\n\n\n.viewer-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 12px 16px;\n background: white;\n border-bottom: 1px solid #e0e0e0;\n flex-shrink: 0;\n}\n\n\n\n.filter-container[_ngcontent-%COMP%] {\n flex: 1;\n max-width: 400px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.filter-icon[_ngcontent-%COMP%] {\n position: absolute;\n left: 12px;\n color: #9e9e9e;\n font-size: 14px;\n pointer-events: none;\n}\n\n.filter-input[_ngcontent-%COMP%] {\n width: 100%;\n padding: 8px 36px 8px 36px;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n font-size: 14px;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.filter-input[_ngcontent-%COMP%]:focus {\n border-color: #1976d2;\n}\n\n.filter-input[_ngcontent-%COMP%]::placeholder {\n color: #9e9e9e;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%] {\n position: absolute;\n right: 8px;\n width: 20px;\n height: 20px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9e9e9e;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn[_ngcontent-%COMP%]:hover {\n background: #f5f5f5;\n color: #616161;\n}\n\n\n\n.record-count[_ngcontent-%COMP%] {\n font-size: 13px;\n color: #757575;\n white-space: nowrap;\n}\n\n\n\n.view-mode-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n.toggle-btn[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #757575;\n transition: all 0.15s ease;\n}\n\n.toggle-btn[_ngcontent-%COMP%]:hover {\n color: #424242;\n}\n\n.toggle-btn.active[_ngcontent-%COMP%] {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n\n\n.timeline-date-selector[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: #616161;\n}\n\n.timeline-date-selector[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #9e9e9e;\n}\n\n.date-field-label[_ngcontent-%COMP%] {\n color: #424242;\n font-weight: 500;\n}\n\n.date-field-select[_ngcontent-%COMP%] {\n padding: 4px 8px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 13px;\n background: white;\n color: #424242;\n cursor: pointer;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.date-field-select[_ngcontent-%COMP%]:hover {\n border-color: #bdbdbd;\n}\n\n.date-field-select[_ngcontent-%COMP%]:focus {\n border-color: #1976d2;\n}\n\n\n\n.timeline-orientation-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n\n\n.viewer-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n position: relative;\n background: white;\n}\n\n\n\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n}\n\n\n\n.loading-overlay[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(255, 255, 255, 0.8);\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n\n\n.loading-container[hidden][_ngcontent-%COMP%], \n.loading-overlay[hidden][_ngcontent-%COMP%] {\n display: none !important;\n}\n\n\n\n.empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #9e9e9e;\n text-align: center;\n}\n\n.empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n}\n\n\n\nmj-entity-cards[hidden][_ngcontent-%COMP%], \nmj-timeline[hidden][_ngcontent-%COMP%] {\n display: none !important;\n}\n\n\n\nmj-entity-cards[_ngcontent-%COMP%]:not([hidden]), \nmj-timeline[_ngcontent-%COMP%]:not([hidden]) {\n display: block;\n height: 100%;\n width: 100%;\n}"] });
1455
1572
  }
1456
1573
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityViewerComponent, [{
1457
1574
  type: Component,
1458
1575
  args: [{ selector: 'mj-entity-viewer', host: {
1459
1576
  'style': 'display: block; height: 100%;'
1460
- }, template: "<div class=\"entity-viewer-container\" [style.height]=\"effectiveConfig.height\">\n <!-- Header -->\n @if (effectiveConfig.showFilter || effectiveConfig.showViewModeToggle || effectiveConfig.showRecordCount) {\n <div class=\"viewer-header\">\n <!-- Filter Input -->\n @if (effectiveConfig.showFilter) {\n <div class=\"filter-container\">\n <i class=\"fa-solid fa-search filter-icon\"></i>\n <input\n type=\"text\"\n class=\"filter-input\"\n [placeholder]=\"effectiveConfig.filterPlaceholder\"\n [value]=\"effectiveFilterText\"\n (input)=\"onFilterChange($any($event.target).value)\"\n />\n @if (effectiveFilterText) {\n <button class=\"clear-filter-btn\" (click)=\"clearFilter()\" title=\"Clear filter\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Record Count -->\n @if (effectiveConfig.showRecordCount && entity) {\n <div class=\"record-count\">\n @if (filteredRecordCount !== totalRecordCount) {\n <span>{{ filteredRecordCount | number }} of {{ totalRecordCount | number }} records</span>\n } @else {\n <span>{{ totalRecordCount | number }} records</span>\n }\n </div>\n }\n\n <!-- View Mode Toggle -->\n @if (effectiveConfig.showViewModeToggle) {\n <div class=\"view-mode-toggle\">\n <button\n class=\"toggle-btn\"\n [class.active]=\"effectiveViewMode === 'grid'\"\n (click)=\"setViewMode('grid')\"\n title=\"Grid View\">\n <i class=\"fa-solid fa-list\"></i>\n </button>\n <button\n class=\"toggle-btn\"\n [class.active]=\"effectiveViewMode === 'cards'\"\n (click)=\"setViewMode('cards')\"\n title=\"Cards View\">\n <i class=\"fa-solid fa-grip\"></i>\n </button>\n @if (hasDateFields) {\n <button\n class=\"toggle-btn\"\n [class.active]=\"effectiveViewMode === 'timeline'\"\n (click)=\"setViewMode('timeline')\"\n title=\"Timeline View\">\n <i class=\"fa-solid fa-timeline\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Timeline Controls (only shown when timeline is active) -->\n @if (effectiveViewMode === 'timeline' && hasDateFields) {\n <!-- Date Field Selector -->\n <div class=\"timeline-date-selector\">\n <i class=\"fa-solid fa-calendar-days\"></i>\n @if (availableDateFields.length === 1) {\n <span class=\"date-field-label\">{{ selectedDateFieldDisplayName }}</span>\n } @else {\n <select\n class=\"date-field-select\"\n [value]=\"selectedTimelineDateField\"\n (change)=\"setTimelineDateField($any($event.target).value)\">\n @for (field of availableDateFields; track field.Name) {\n <option [value]=\"field.Name\">{{ field.DisplayNameOrName }}</option>\n }\n </select>\n }\n </div>\n\n <!-- Orientation Toggle -->\n <div class=\"timeline-orientation-toggle\">\n <button\n class=\"toggle-btn\"\n (click)=\"toggleTimelineOrientation()\"\n [title]=\"timelineOrientation === 'vertical' ? 'Switch to Horizontal' : 'Switch to Vertical'\">\n <i [class]=\"timelineOrientation === 'vertical' ? 'fa-solid fa-ellipsis-vertical' : 'fa-solid fa-ellipsis'\"></i>\n </button>\n </div>\n\n <!-- Sort Order Toggle -->\n <div class=\"timeline-sort-toggle\">\n <button\n class=\"toggle-btn\"\n (click)=\"toggleTimelineSortOrder()\"\n [title]=\"timelineSortOrder === 'desc' ? 'Showing Newest First (click for Oldest First)' : 'Showing Oldest First (click for Newest First)'\">\n <i [class]=\"timelineSortOrder === 'desc' ? 'fa-solid fa-arrow-down-wide-short' : 'fa-solid fa-arrow-up-wide-short'\"></i>\n </button>\n </div>\n }\n </div>\n }\n\n <!-- Content -->\n <div class=\"viewer-content\">\n <!-- Loading container - full page when no data exists -->\n <div class=\"loading-container\" [hidden]=\"!(isLoading && filteredRecords.length === 0)\">\n <mj-loading [text]=\"loadingMessage\" size=\"medium\"></mj-loading>\n </div>\n\n <!-- Loading overlay - shown on top of content when loading with existing data -->\n <div class=\"loading-overlay\" [hidden]=\"!(isLoading && filteredRecords.length > 0)\">\n <mj-loading [text]=\"loadingMessage\" size=\"small\"></mj-loading>\n </div>\n\n <!-- Empty state: no entity selected -->\n <div class=\"empty-state\" [hidden]=\"!!entity\">\n <i class=\"fa-solid fa-database\"></i>\n <p>Select an entity to view records</p>\n </div>\n\n <!-- Empty state: no records found -->\n <div class=\"empty-state\" [hidden]=\"!entity || filteredRecords.length > 0 || isLoading\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>{{ debouncedFilterText ? 'No matching records' : 'No records found' }}</p>\n </div>\n\n <!-- Grid View - always rendered, visibility controlled by hidden -->\n <mj-entity-data-grid\n [hidden]=\"effectiveViewMode !== 'grid' || !entity\"\n [Data]=\"filteredRecords\"\n [Params]=\"gridParams\"\n [FilterText]=\"debouncedFilterText\"\n [GridState]=\"gridState\"\n [Height]=\"'auto'\"\n [ShowToolbar]=\"showGridToolbar\"\n [ToolbarConfig]=\"effectiveGridToolbarConfig\"\n [SelectionMode]=\"gridSelectionMode\"\n [ShowAddToListButton]=\"showAddToListButton\"\n [AllowLoad]=\"false\"\n (AfterRowClick)=\"onDataGridRowClick($event)\"\n (AfterRowDoubleClick)=\"onDataGridRowDoubleClick($event)\"\n (AfterSort)=\"onDataGridSortChanged($event)\"\n (GridStateChanged)=\"onGridStateChanged($event)\"\n (SelectionChange)=\"onGridSelectionChange($event)\"\n (NewButtonClick)=\"onGridAddRequested()\"\n (RefreshButtonClick)=\"onGridRefreshRequested()\"\n (DeleteButtonClick)=\"onGridDeleteRequested($event)\"\n (ExportButtonClick)=\"onGridExportRequested()\"\n (AddToListRequested)=\"onGridAddToListRequested($event)\">\n </mj-entity-data-grid>\n\n <!-- Cards View - always rendered, visibility controlled by hidden -->\n <mj-entity-cards\n [hidden]=\"effectiveViewMode !== 'cards' || !entity\"\n [entity]=\"entity\"\n [records]=\"filteredRecords\"\n [selectedRecordId]=\"selectedRecordId\"\n [cardTemplate]=\"cardTemplate\"\n [hiddenFieldMatches]=\"hiddenFieldMatches\"\n [filterText]=\"debouncedFilterText\"\n (recordSelected)=\"onRecordSelected($event)\"\n (recordOpened)=\"onRecordOpened($event)\">\n </mj-entity-cards>\n\n <!-- Timeline View - always rendered when date fields exist, visibility controlled by hidden -->\n <mj-timeline\n [hidden]=\"effectiveViewMode !== 'timeline' || !hasDateFields\"\n [groups]=\"timelineGroups\"\n [orientation]=\"timelineOrientation\"\n [layout]=\"timelineOrientation === 'vertical' ? 'alternating' : 'single'\"\n [sortOrder]=\"timelineSortOrder\"\n [segmentGrouping]=\"timelineSegmentGrouping\"\n [segmentsCollapsible]=\"true\"\n [segmentsDefaultExpanded]=\"true\"\n [selectedEventId]=\"timelineSelectedEventId\"\n (afterEventClick)=\"onTimelineEventClick($event)\">\n </mj-timeline>\n </div>\n\n <!-- Pagination - only show when there's more data to load OR we've loaded more than one page -->\n @if (effectiveConfig.showPagination && entity && (pagination.hasMore || pagination.totalRecords > effectiveConfig.pageSize)) {\n <mj-pagination\n [pagination]=\"pagination\"\n [loadedRecordCount]=\"filteredRecords.length\"\n (loadMore)=\"onLoadMore()\">\n </mj-pagination>\n }\n</div>\n", styles: [".entity-viewer-container {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: #fafafa;\n}\n\n/* Pagination footer - always visible at bottom */\nmj-pagination {\n flex-shrink: 0;\n}\n\n/* Header */\n.viewer-header {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 12px 16px;\n background: white;\n border-bottom: 1px solid #e0e0e0;\n flex-shrink: 0;\n}\n\n/* Filter */\n.filter-container {\n flex: 1;\n max-width: 400px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.filter-icon {\n position: absolute;\n left: 12px;\n color: #9e9e9e;\n font-size: 14px;\n pointer-events: none;\n}\n\n.filter-input {\n width: 100%;\n padding: 8px 36px 8px 36px;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n font-size: 14px;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.filter-input:focus {\n border-color: #1976d2;\n}\n\n.filter-input::placeholder {\n color: #9e9e9e;\n}\n\n.clear-filter-btn {\n position: absolute;\n right: 8px;\n width: 20px;\n height: 20px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9e9e9e;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn:hover {\n background: #f5f5f5;\n color: #616161;\n}\n\n/* Record Count */\n.record-count {\n font-size: 13px;\n color: #757575;\n white-space: nowrap;\n}\n\n/* View Mode Toggle */\n.view-mode-toggle {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n.toggle-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #757575;\n transition: all 0.15s ease;\n}\n\n.toggle-btn:hover {\n color: #424242;\n}\n\n.toggle-btn.active {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n/* Timeline Date Field Selector */\n.timeline-date-selector {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: #616161;\n}\n\n.timeline-date-selector i {\n color: #9e9e9e;\n}\n\n.date-field-label {\n color: #424242;\n font-weight: 500;\n}\n\n.date-field-select {\n padding: 4px 8px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 13px;\n background: white;\n color: #424242;\n cursor: pointer;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.date-field-select:hover {\n border-color: #bdbdbd;\n}\n\n.date-field-select:focus {\n border-color: #1976d2;\n}\n\n/* Timeline Orientation Toggle */\n.timeline-orientation-toggle {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n/* Content */\n.viewer-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n position: relative;\n background: white;\n}\n\n/* Loading State - positioned as overlay when data exists */\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n}\n\n/* Loading overlay when shown on top of content */\n.loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(255, 255, 255, 0.8);\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Empty State */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #9e9e9e;\n text-align: center;\n}\n\n.empty-state i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\n\n.empty-state p {\n margin: 0;\n font-size: 14px;\n}\n\n/* Hidden components should not display - ensure [hidden] attribute works properly */\nmj-entity-cards[hidden],\nmj-timeline[hidden] {\n display: none !important;\n}\n\n/* Visible view components should fill available space */\nmj-entity-cards:not([hidden]),\nmj-timeline:not([hidden]) {\n display: block;\n height: 100%;\n width: 100%;\n}\n"] }]
1577
+ }, template: "<div class=\"entity-viewer-container\" [style.height]=\"effectiveConfig.height\">\n <!-- Header -->\n @if (effectiveConfig.showFilter || effectiveConfig.showViewModeToggle || effectiveConfig.showRecordCount) {\n <div class=\"viewer-header\">\n <!-- Filter Input -->\n @if (effectiveConfig.showFilter) {\n <div class=\"filter-container\">\n <i class=\"fa-solid fa-search filter-icon\"></i>\n <input\n type=\"text\"\n class=\"filter-input\"\n [placeholder]=\"effectiveConfig.filterPlaceholder\"\n [value]=\"effectiveFilterText\"\n (input)=\"onFilterChange($any($event.target).value)\"\n />\n @if (effectiveFilterText) {\n <button class=\"clear-filter-btn\" (click)=\"clearFilter()\" title=\"Clear filter\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Record Count -->\n @if (effectiveConfig.showRecordCount && effectiveEntity) {\n <div class=\"record-count\">\n @if (filteredRecordCount !== totalRecordCount) {\n <span>{{ filteredRecordCount | number }} of {{ totalRecordCount | number }} records</span>\n } @else {\n <span>{{ totalRecordCount | number }} records</span>\n }\n </div>\n }\n\n <!-- View Mode Toggle -->\n @if (effectiveConfig.showViewModeToggle) {\n <div class=\"view-mode-toggle\">\n <button\n class=\"toggle-btn\"\n [class.active]=\"effectiveViewMode === 'grid'\"\n (click)=\"setViewMode('grid')\"\n title=\"Grid View\">\n <i class=\"fa-solid fa-list\"></i>\n </button>\n <button\n class=\"toggle-btn\"\n [class.active]=\"effectiveViewMode === 'cards'\"\n (click)=\"setViewMode('cards')\"\n title=\"Cards View\">\n <i class=\"fa-solid fa-grip\"></i>\n </button>\n @if (hasDateFields) {\n <button\n class=\"toggle-btn\"\n [class.active]=\"effectiveViewMode === 'timeline'\"\n (click)=\"setViewMode('timeline')\"\n title=\"Timeline View\">\n <i class=\"fa-solid fa-timeline\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Timeline Controls (only shown when timeline is active) -->\n @if (effectiveViewMode === 'timeline' && hasDateFields) {\n <!-- Date Field Selector -->\n <div class=\"timeline-date-selector\">\n <i class=\"fa-solid fa-calendar-days\"></i>\n @if (availableDateFields.length === 1) {\n <span class=\"date-field-label\">{{ selectedDateFieldDisplayName }}</span>\n } @else {\n <select\n class=\"date-field-select\"\n [value]=\"selectedTimelineDateField\"\n (change)=\"setTimelineDateField($any($event.target).value)\">\n @for (field of availableDateFields; track field.Name) {\n <option [value]=\"field.Name\">{{ field.DisplayNameOrName }}</option>\n }\n </select>\n }\n </div>\n\n <!-- Orientation Toggle -->\n <div class=\"timeline-orientation-toggle\">\n <button\n class=\"toggle-btn\"\n (click)=\"toggleTimelineOrientation()\"\n [title]=\"timelineOrientation === 'vertical' ? 'Switch to Horizontal' : 'Switch to Vertical'\">\n <i [class]=\"timelineOrientation === 'vertical' ? 'fa-solid fa-ellipsis-vertical' : 'fa-solid fa-ellipsis'\"></i>\n </button>\n </div>\n\n <!-- Sort Order Toggle -->\n <div class=\"timeline-sort-toggle\">\n <button\n class=\"toggle-btn\"\n (click)=\"toggleTimelineSortOrder()\"\n [title]=\"timelineSortOrder === 'desc' ? 'Showing Newest First (click for Oldest First)' : 'Showing Oldest First (click for Newest First)'\">\n <i [class]=\"timelineSortOrder === 'desc' ? 'fa-solid fa-arrow-down-wide-short' : 'fa-solid fa-arrow-up-wide-short'\"></i>\n </button>\n </div>\n }\n </div>\n }\n\n <!-- Content -->\n <div class=\"viewer-content\">\n <!-- Loading container - full page when no data exists -->\n <div class=\"loading-container\" [hidden]=\"!(isLoading && filteredRecords.length === 0)\">\n <mj-loading [text]=\"loadingMessage\" size=\"medium\"></mj-loading>\n </div>\n\n <!-- Loading overlay - shown on top of content when loading with existing data -->\n <div class=\"loading-overlay\" [hidden]=\"!(isLoading && filteredRecords.length > 0)\">\n <mj-loading [text]=\"loadingMessage\" size=\"small\"></mj-loading>\n </div>\n\n <!-- Empty state: no entity selected -->\n <div class=\"empty-state\" [hidden]=\"!!effectiveEntity\">\n <i class=\"fa-solid fa-database\"></i>\n <p>Select an entity to view records</p>\n </div>\n\n <!-- Empty state: no records found -->\n <div class=\"empty-state\" [hidden]=\"!effectiveEntity || filteredRecords.length > 0 || isLoading\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>{{ debouncedFilterText ? 'No matching records' : 'No records found' }}</p>\n </div>\n\n <!-- Grid View - always rendered, visibility controlled by hidden -->\n <mj-entity-data-grid\n [hidden]=\"effectiveViewMode !== 'grid' || !effectiveEntity\"\n [Data]=\"filteredRecords\"\n [Params]=\"gridParams\"\n [FilterText]=\"debouncedFilterText\"\n [GridState]=\"gridState\"\n [Height]=\"'auto'\"\n [ShowToolbar]=\"showGridToolbar\"\n [ToolbarConfig]=\"effectiveGridToolbarConfig\"\n [SelectionMode]=\"gridSelectionMode\"\n [ShowAddToListButton]=\"showAddToListButton\"\n [AllowLoad]=\"false\"\n (AfterRowClick)=\"onDataGridRowClick($event)\"\n (AfterRowDoubleClick)=\"onDataGridRowDoubleClick($event)\"\n (AfterSort)=\"onDataGridSortChanged($event)\"\n (GridStateChanged)=\"onGridStateChanged($event)\"\n (SelectionChange)=\"onGridSelectionChange($event)\"\n (NewButtonClick)=\"onGridAddRequested()\"\n (RefreshButtonClick)=\"onGridRefreshRequested()\"\n (DeleteButtonClick)=\"onGridDeleteRequested($event)\"\n (ExportButtonClick)=\"onGridExportRequested()\"\n (AddToListRequested)=\"onGridAddToListRequested($event)\">\n </mj-entity-data-grid>\n\n <!-- Cards View - always rendered, visibility controlled by hidden -->\n <mj-entity-cards\n [hidden]=\"effectiveViewMode !== 'cards' || !effectiveEntity\"\n [entity]=\"effectiveEntity\"\n [records]=\"filteredRecords\"\n [selectedRecordId]=\"selectedRecordId\"\n [cardTemplate]=\"cardTemplate\"\n [hiddenFieldMatches]=\"hiddenFieldMatches\"\n [filterText]=\"debouncedFilterText\"\n (recordSelected)=\"onRecordSelected($event)\"\n (recordOpened)=\"onRecordOpened($event)\">\n </mj-entity-cards>\n\n <!-- Timeline View - always rendered when date fields exist, visibility controlled by hidden -->\n <mj-timeline\n [hidden]=\"effectiveViewMode !== 'timeline' || !hasDateFields\"\n [groups]=\"timelineGroups\"\n [orientation]=\"timelineOrientation\"\n [layout]=\"timelineOrientation === 'vertical' ? 'alternating' : 'single'\"\n [sortOrder]=\"timelineSortOrder\"\n [segmentGrouping]=\"timelineSegmentGrouping\"\n [segmentsCollapsible]=\"true\"\n [segmentsDefaultExpanded]=\"true\"\n [selectedEventId]=\"timelineSelectedEventId\"\n (afterEventClick)=\"onTimelineEventClick($event)\">\n </mj-timeline>\n </div>\n\n <!-- Pagination - only show when there's more data to load OR we've loaded more than one page -->\n @if (effectiveConfig.showPagination && effectiveEntity && (pagination.hasMore || pagination.totalRecords > effectiveConfig.pageSize)) {\n <mj-pagination\n [pagination]=\"pagination\"\n [loadedRecordCount]=\"filteredRecords.length\"\n (loadMore)=\"onLoadMore()\">\n </mj-pagination>\n }\n</div>\n", styles: [".entity-viewer-container {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: #fafafa;\n}\n\n/* Pagination footer - always visible at bottom */\nmj-pagination {\n flex-shrink: 0;\n}\n\n/* Header */\n.viewer-header {\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 12px 16px;\n background: white;\n border-bottom: 1px solid #e0e0e0;\n flex-shrink: 0;\n}\n\n/* Filter */\n.filter-container {\n flex: 1;\n max-width: 400px;\n position: relative;\n display: flex;\n align-items: center;\n}\n\n.filter-icon {\n position: absolute;\n left: 12px;\n color: #9e9e9e;\n font-size: 14px;\n pointer-events: none;\n}\n\n.filter-input {\n width: 100%;\n padding: 8px 36px 8px 36px;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n font-size: 14px;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.filter-input:focus {\n border-color: #1976d2;\n}\n\n.filter-input::placeholder {\n color: #9e9e9e;\n}\n\n.clear-filter-btn {\n position: absolute;\n right: 8px;\n width: 20px;\n height: 20px;\n border: none;\n background: transparent;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #9e9e9e;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn:hover {\n background: #f5f5f5;\n color: #616161;\n}\n\n/* Record Count */\n.record-count {\n font-size: 13px;\n color: #757575;\n white-space: nowrap;\n}\n\n/* View Mode Toggle */\n.view-mode-toggle {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n.toggle-btn {\n width: 32px;\n height: 32px;\n border: none;\n background: transparent;\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #757575;\n transition: all 0.15s ease;\n}\n\n.toggle-btn:hover {\n color: #424242;\n}\n\n.toggle-btn.active {\n background: white;\n color: #1976d2;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n/* Timeline Date Field Selector */\n.timeline-date-selector {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: #616161;\n}\n\n.timeline-date-selector i {\n color: #9e9e9e;\n}\n\n.date-field-label {\n color: #424242;\n font-weight: 500;\n}\n\n.date-field-select {\n padding: 4px 8px;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n font-size: 13px;\n background: white;\n color: #424242;\n cursor: pointer;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.date-field-select:hover {\n border-color: #bdbdbd;\n}\n\n.date-field-select:focus {\n border-color: #1976d2;\n}\n\n/* Timeline Orientation Toggle */\n.timeline-orientation-toggle {\n display: flex;\n background: #f5f5f5;\n border-radius: 6px;\n padding: 2px;\n}\n\n/* Content */\n.viewer-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n position: relative;\n background: white;\n}\n\n/* Loading State - full-page centered loading for initial load when no data exists */\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n}\n\n/* Loading overlay - semi-transparent overlay on top of existing content during refresh */\n.loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(255, 255, 255, 0.8);\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Ensure [hidden] attribute works properly on loading elements */\n.loading-container[hidden],\n.loading-overlay[hidden] {\n display: none !important;\n}\n\n/* Empty State */\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #9e9e9e;\n text-align: center;\n}\n\n.empty-state i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\n\n.empty-state p {\n margin: 0;\n font-size: 14px;\n}\n\n/* Hidden components should not display - ensure [hidden] attribute works properly */\nmj-entity-cards[hidden],\nmj-timeline[hidden] {\n display: none !important;\n}\n\n/* Visible view components should fill available space */\nmj-entity-cards:not([hidden]),\nmj-timeline:not([hidden]) {\n display: block;\n height: 100%;\n width: 100%;\n}\n"] }]
1461
1578
  }], () => [{ type: i0.ChangeDetectorRef }], { entity: [{
1462
1579
  type: Input
1463
1580
  }], records: [{
@@ -1524,5 +1641,5 @@ export class EntityViewerComponent {
1524
1641
  type: ViewChild,
1525
1642
  args: [EntityDataGridComponent]
1526
1643
  }] }); })();
1527
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityViewerComponent, { className: "EntityViewerComponent" }); })();
1644
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityViewerComponent, { className: "EntityViewerComponent", filePath: "src/lib/entity-viewer/entity-viewer.component.ts", lineNumber: 76 }); })();
1528
1645
  //# sourceMappingURL=entity-viewer.component.js.map