@memberjunction/ng-entity-viewer 2.122.2 → 2.123.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.
@@ -1,14 +1,18 @@
1
1
  import { Component, Input, Output, EventEmitter } from '@angular/core';
2
2
  import { Subject } from 'rxjs';
3
3
  import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
4
- import { RunView } from '@memberjunction/core';
4
+ import { EntityFieldTSType, RunView } from '@memberjunction/core';
5
+ import { TimelineGroup } from '@memberjunction/ng-timeline';
5
6
  import { DEFAULT_VIEWER_CONFIG } from '../types';
6
7
  import * as i0 from "@angular/core";
7
- import * as i1 from "@memberjunction/ng-shared-generic";
8
- import * as i2 from "../entity-grid/entity-grid.component";
9
- import * as i3 from "../entity-cards/entity-cards.component";
10
- import * as i4 from "../pagination/pagination.component";
11
- import * as i5 from "@angular/common";
8
+ import * as i1 from "@angular/forms";
9
+ import * as i2 from "@memberjunction/ng-shared-generic";
10
+ import * as i3 from "@memberjunction/ng-timeline";
11
+ import * as i4 from "../entity-grid/entity-grid.component";
12
+ import * as i5 from "../entity-cards/entity-cards.component";
13
+ import * as i6 from "../pagination/pagination.component";
14
+ import * as i7 from "@angular/common";
15
+ const _forTrack0 = ($index, $item) => $item.Name;
12
16
  function EntityViewerComponent_Conditional_1_Conditional_1_Conditional_3_Template(rf, ctx) { if (rf & 1) {
13
17
  const _r3 = i0.ɵɵgetCurrentView();
14
18
  i0.ɵɵelementStart(0, "button", 12);
@@ -62,6 +66,16 @@ function EntityViewerComponent_Conditional_1_Conditional_2_Template(rf, ctx) { i
62
66
  i0.ɵɵadvance();
63
67
  i0.ɵɵconditional(ctx_r1.filteredRecordCount !== ctx_r1.totalRecordCount ? 1 : 2);
64
68
  } }
69
+ function EntityViewerComponent_Conditional_1_Conditional_3_Conditional_5_Template(rf, ctx) { if (rf & 1) {
70
+ const _r5 = i0.ɵɵgetCurrentView();
71
+ i0.ɵɵelementStart(0, "button", 19);
72
+ i0.ɵɵlistener("click", function EntityViewerComponent_Conditional_1_Conditional_3_Conditional_5_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.setViewMode("timeline")); });
73
+ i0.ɵɵelement(1, "i", 20);
74
+ i0.ɵɵelementEnd();
75
+ } if (rf & 2) {
76
+ const ctx_r1 = i0.ɵɵnextContext(3);
77
+ i0.ɵɵclassProp("active", ctx_r1.effectiveViewMode === "timeline");
78
+ } }
65
79
  function EntityViewerComponent_Conditional_1_Conditional_3_Template(rf, ctx) { if (rf & 1) {
66
80
  const _r4 = i0.ɵɵgetCurrentView();
67
81
  i0.ɵɵelementStart(0, "div", 8)(1, "button", 14);
@@ -71,17 +85,79 @@ function EntityViewerComponent_Conditional_1_Conditional_3_Template(rf, ctx) { i
71
85
  i0.ɵɵelementStart(3, "button", 16);
72
86
  i0.ɵɵlistener("click", function EntityViewerComponent_Conditional_1_Conditional_3_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.setViewMode("cards")); });
73
87
  i0.ɵɵelement(4, "i", 17);
74
- i0.ɵɵelementEnd()();
88
+ i0.ɵɵelementEnd();
89
+ i0.ɵɵtemplate(5, EntityViewerComponent_Conditional_1_Conditional_3_Conditional_5_Template, 2, 2, "button", 18);
90
+ i0.ɵɵelementEnd();
75
91
  } if (rf & 2) {
76
92
  const ctx_r1 = i0.ɵɵnextContext(2);
77
93
  i0.ɵɵadvance();
78
94
  i0.ɵɵclassProp("active", ctx_r1.effectiveViewMode === "grid");
79
95
  i0.ɵɵadvance(2);
80
96
  i0.ɵɵclassProp("active", ctx_r1.effectiveViewMode === "cards");
97
+ i0.ɵɵadvance(2);
98
+ i0.ɵɵconditional(ctx_r1.hasDateFields ? 5 : -1);
99
+ } }
100
+ function EntityViewerComponent_Conditional_1_Conditional_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
101
+ i0.ɵɵelementStart(0, "span", 23);
102
+ i0.ɵɵtext(1);
103
+ i0.ɵɵelementEnd();
104
+ } if (rf & 2) {
105
+ const ctx_r1 = i0.ɵɵnextContext(3);
106
+ i0.ɵɵadvance();
107
+ i0.ɵɵtextInterpolate(ctx_r1.selectedDateFieldDisplayName);
108
+ } }
109
+ function EntityViewerComponent_Conditional_1_Conditional_4_Conditional_3_For_2_Template(rf, ctx) { if (rf & 1) {
110
+ i0.ɵɵelementStart(0, "option", 29);
111
+ i0.ɵɵtext(1);
112
+ i0.ɵɵelementEnd();
113
+ } if (rf & 2) {
114
+ const field_r8 = ctx.$implicit;
115
+ i0.ɵɵproperty("value", field_r8.Name);
116
+ i0.ɵɵadvance();
117
+ i0.ɵɵtextInterpolate(field_r8.DisplayNameOrName);
118
+ } }
119
+ function EntityViewerComponent_Conditional_1_Conditional_4_Conditional_3_Template(rf, ctx) { if (rf & 1) {
120
+ const _r7 = i0.ɵɵgetCurrentView();
121
+ i0.ɵɵelementStart(0, "select", 28);
122
+ i0.ɵɵlistener("change", function EntityViewerComponent_Conditional_1_Conditional_4_Conditional_3_Template_select_change_0_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.setTimelineDateField($event.target.value)); });
123
+ i0.ɵɵrepeaterCreate(1, EntityViewerComponent_Conditional_1_Conditional_4_Conditional_3_For_2_Template, 2, 2, "option", 29, _forTrack0);
124
+ i0.ɵɵelementEnd();
125
+ } if (rf & 2) {
126
+ const ctx_r1 = i0.ɵɵnextContext(3);
127
+ i0.ɵɵproperty("value", ctx_r1.selectedTimelineDateField);
128
+ i0.ɵɵadvance();
129
+ i0.ɵɵrepeater(ctx_r1.availableDateFields);
130
+ } }
131
+ function EntityViewerComponent_Conditional_1_Conditional_4_Template(rf, ctx) { if (rf & 1) {
132
+ const _r6 = i0.ɵɵgetCurrentView();
133
+ i0.ɵɵelementStart(0, "div", 21);
134
+ i0.ɵɵelement(1, "i", 22);
135
+ i0.ɵɵtemplate(2, EntityViewerComponent_Conditional_1_Conditional_4_Conditional_2_Template, 2, 1, "span", 23)(3, EntityViewerComponent_Conditional_1_Conditional_4_Conditional_3_Template, 3, 1, "select", 24);
136
+ i0.ɵɵelementEnd();
137
+ i0.ɵɵelementStart(4, "div", 25)(5, "button", 26);
138
+ i0.ɵɵlistener("click", function EntityViewerComponent_Conditional_1_Conditional_4_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.toggleTimelineOrientation()); });
139
+ i0.ɵɵelement(6, "i");
140
+ i0.ɵɵelementEnd()();
141
+ i0.ɵɵelementStart(7, "div", 27)(8, "button", 26);
142
+ i0.ɵɵlistener("click", function EntityViewerComponent_Conditional_1_Conditional_4_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.toggleTimelineSortOrder()); });
143
+ i0.ɵɵelement(9, "i");
144
+ i0.ɵɵelementEnd()();
145
+ } if (rf & 2) {
146
+ const ctx_r1 = i0.ɵɵnextContext(2);
147
+ i0.ɵɵadvance(2);
148
+ i0.ɵɵconditional(ctx_r1.availableDateFields.length === 1 ? 2 : 3);
149
+ i0.ɵɵadvance(3);
150
+ i0.ɵɵproperty("title", ctx_r1.timelineOrientation === "vertical" ? "Switch to Horizontal" : "Switch to Vertical");
151
+ i0.ɵɵadvance();
152
+ i0.ɵɵclassMap(ctx_r1.timelineOrientation === "vertical" ? "fa-solid fa-ellipsis-vertical" : "fa-solid fa-ellipsis");
153
+ i0.ɵɵadvance(2);
154
+ i0.ɵɵproperty("title", ctx_r1.timelineSortOrder === "desc" ? "Showing Newest First (click for Oldest First)" : "Showing Oldest First (click for Newest First)");
155
+ i0.ɵɵadvance();
156
+ i0.ɵɵclassMap(ctx_r1.timelineSortOrder === "desc" ? "fa-solid fa-arrow-down-wide-short" : "fa-solid fa-arrow-up-wide-short");
81
157
  } }
82
158
  function EntityViewerComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
83
159
  i0.ɵɵelementStart(0, "div", 1);
84
- i0.ɵɵtemplate(1, EntityViewerComponent_Conditional_1_Conditional_1_Template, 4, 3, "div", 6)(2, EntityViewerComponent_Conditional_1_Conditional_2_Template, 3, 1, "div", 7)(3, EntityViewerComponent_Conditional_1_Conditional_3_Template, 5, 4, "div", 8);
160
+ i0.ɵɵtemplate(1, EntityViewerComponent_Conditional_1_Conditional_1_Template, 4, 3, "div", 6)(2, EntityViewerComponent_Conditional_1_Conditional_2_Template, 3, 1, "div", 7)(3, EntityViewerComponent_Conditional_1_Conditional_3_Template, 6, 5, "div", 8)(4, EntityViewerComponent_Conditional_1_Conditional_4_Template, 10, 7);
85
161
  i0.ɵɵelementEnd();
86
162
  } if (rf & 2) {
87
163
  const ctx_r1 = i0.ɵɵnextContext();
@@ -91,10 +167,12 @@ function EntityViewerComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
91
167
  i0.ɵɵconditional(ctx_r1.effectiveConfig.showRecordCount && ctx_r1.entity ? 2 : -1);
92
168
  i0.ɵɵadvance();
93
169
  i0.ɵɵconditional(ctx_r1.effectiveConfig.showViewModeToggle ? 3 : -1);
170
+ i0.ɵɵadvance();
171
+ i0.ɵɵconditional(ctx_r1.effectiveViewMode === "timeline" && ctx_r1.hasDateFields ? 4 : -1);
94
172
  } }
95
173
  function EntityViewerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
96
174
  i0.ɵɵelementStart(0, "div", 3);
97
- i0.ɵɵelement(1, "mj-loading", 18);
175
+ i0.ɵɵelement(1, "mj-loading", 30);
98
176
  i0.ɵɵelementEnd();
99
177
  } if (rf & 2) {
100
178
  const ctx_r1 = i0.ɵɵnextContext();
@@ -103,14 +181,14 @@ function EntityViewerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
103
181
  } }
104
182
  function EntityViewerComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
105
183
  i0.ɵɵelementStart(0, "div", 4);
106
- i0.ɵɵelement(1, "i", 19);
184
+ i0.ɵɵelement(1, "i", 31);
107
185
  i0.ɵɵelementStart(2, "p");
108
186
  i0.ɵɵtext(3, "Select an entity to view records");
109
187
  i0.ɵɵelementEnd()();
110
188
  } }
111
189
  function EntityViewerComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
112
190
  i0.ɵɵelementStart(0, "div", 4);
113
- i0.ɵɵelement(1, "i", 20);
191
+ i0.ɵɵelement(1, "i", 32);
114
192
  i0.ɵɵelementStart(2, "p");
115
193
  i0.ɵɵtext(3);
116
194
  i0.ɵɵelementEnd()();
@@ -119,34 +197,36 @@ function EntityViewerComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) {
119
197
  i0.ɵɵadvance(3);
120
198
  i0.ɵɵtextInterpolate(ctx_r1.debouncedFilterText ? "No matching records" : "No records found");
121
199
  } }
122
- function EntityViewerComponent_Conditional_6_Conditional_0_Template(rf, ctx) { if (rf & 1) {
123
- const _r5 = i0.ɵɵgetCurrentView();
124
- i0.ɵɵelementStart(0, "mj-entity-grid", 23);
125
- i0.ɵɵlistener("recordSelected", function EntityViewerComponent_Conditional_6_Conditional_0_Template_mj_entity_grid_recordSelected_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onRecordSelected($event)); })("recordOpened", function EntityViewerComponent_Conditional_6_Conditional_0_Template_mj_entity_grid_recordOpened_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onRecordOpened($event)); })("sortChanged", function EntityViewerComponent_Conditional_6_Conditional_0_Template_mj_entity_grid_sortChanged_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onSortChanged($event)); })("gridStateChanged", function EntityViewerComponent_Conditional_6_Conditional_0_Template_mj_entity_grid_gridStateChanged_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onGridStateChanged($event)); });
126
- i0.ɵɵelementEnd();
127
- } if (rf & 2) {
128
- const ctx_r1 = i0.ɵɵnextContext(2);
129
- i0.ɵɵproperty("entity", ctx_r1.entity)("records", ctx_r1.filteredRecords)("selectedRecordId", ctx_r1.selectedRecordId)("columns", ctx_r1.gridColumns)("height", "100%")("filterText", ctx_r1.debouncedFilterText)("sortState", ctx_r1.effectiveSortState)("serverSideSorting", ctx_r1.effectiveConfig.serverSideSorting)("gridState", ctx_r1.gridState);
130
- } }
131
- function EntityViewerComponent_Conditional_6_Conditional_1_Template(rf, ctx) { if (rf & 1) {
132
- const _r6 = i0.ɵɵgetCurrentView();
133
- i0.ɵɵelementStart(0, "mj-entity-cards", 24);
134
- i0.ɵɵlistener("recordSelected", function EntityViewerComponent_Conditional_6_Conditional_1_Template_mj_entity_cards_recordSelected_0_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onRecordSelected($event)); })("recordOpened", function EntityViewerComponent_Conditional_6_Conditional_1_Template_mj_entity_cards_recordOpened_0_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onRecordOpened($event)); });
200
+ function EntityViewerComponent_Conditional_6_Conditional_2_Template(rf, ctx) { if (rf & 1) {
201
+ const _r10 = i0.ɵɵgetCurrentView();
202
+ i0.ɵɵelementStart(0, "mj-timeline", 36);
203
+ i0.ɵɵlistener("afterEventClick", function EntityViewerComponent_Conditional_6_Conditional_2_Template_mj_timeline_afterEventClick_0_listener($event) { i0.ɵɵrestoreView(_r10); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onTimelineEventClick($event)); });
135
204
  i0.ɵɵelementEnd();
136
205
  } if (rf & 2) {
137
206
  const ctx_r1 = i0.ɵɵnextContext(2);
138
- i0.ɵɵproperty("entity", ctx_r1.entity)("records", ctx_r1.filteredRecords)("selectedRecordId", ctx_r1.selectedRecordId)("cardTemplate", ctx_r1.cardTemplate)("hiddenFieldMatches", ctx_r1.hiddenFieldMatches)("filterText", ctx_r1.debouncedFilterText);
207
+ i0.ɵɵproperty("groups", ctx_r1.timelineGroups)("orientation", ctx_r1.timelineOrientation)("layout", ctx_r1.timelineOrientation === "vertical" ? "alternating" : "single")("sortOrder", ctx_r1.timelineSortOrder)("segmentGrouping", ctx_r1.timelineSegmentGrouping)("segmentsCollapsible", true)("segmentsDefaultExpanded", true)("selectedEventId", ctx_r1.timelineSelectedEventId);
139
208
  } }
140
209
  function EntityViewerComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
141
- i0.ɵɵtemplate(0, EntityViewerComponent_Conditional_6_Conditional_0_Template, 1, 9, "mj-entity-grid", 21)(1, EntityViewerComponent_Conditional_6_Conditional_1_Template, 1, 6, "mj-entity-cards", 22);
210
+ const _r9 = i0.ɵɵgetCurrentView();
211
+ i0.ɵɵelementStart(0, "mj-entity-grid", 33);
212
+ i0.ɵɵlistener("recordSelected", function EntityViewerComponent_Conditional_6_Template_mj_entity_grid_recordSelected_0_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRecordSelected($event)); })("recordOpened", function EntityViewerComponent_Conditional_6_Template_mj_entity_grid_recordOpened_0_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRecordOpened($event)); })("sortChanged", function EntityViewerComponent_Conditional_6_Template_mj_entity_grid_sortChanged_0_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onSortChanged($event)); })("gridStateChanged", function EntityViewerComponent_Conditional_6_Template_mj_entity_grid_gridStateChanged_0_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onGridStateChanged($event)); });
213
+ i0.ɵɵelementEnd();
214
+ i0.ɵɵelementStart(1, "mj-entity-cards", 34);
215
+ i0.ɵɵlistener("recordSelected", function EntityViewerComponent_Conditional_6_Template_mj_entity_cards_recordSelected_1_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRecordSelected($event)); })("recordOpened", function EntityViewerComponent_Conditional_6_Template_mj_entity_cards_recordOpened_1_listener($event) { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRecordOpened($event)); });
216
+ i0.ɵɵelementEnd();
217
+ i0.ɵɵtemplate(2, EntityViewerComponent_Conditional_6_Conditional_2_Template, 1, 8, "mj-timeline", 35);
142
218
  } if (rf & 2) {
143
219
  const ctx_r1 = i0.ɵɵnextContext();
144
- i0.ɵɵconditional(ctx_r1.effectiveViewMode === "grid" ? 0 : 1);
220
+ i0.ɵɵproperty("hidden", ctx_r1.effectiveViewMode !== "grid")("entity", ctx_r1.entity)("records", ctx_r1.filteredRecords)("selectedRecordId", ctx_r1.selectedRecordId)("columns", ctx_r1.gridColumns)("height", "100%")("filterText", ctx_r1.debouncedFilterText)("sortState", ctx_r1.effectiveSortState)("serverSideSorting", ctx_r1.effectiveConfig.serverSideSorting)("gridState", ctx_r1.gridState);
221
+ i0.ɵɵadvance();
222
+ i0.ɵɵproperty("hidden", ctx_r1.effectiveViewMode !== "cards")("entity", ctx_r1.entity)("records", ctx_r1.filteredRecords)("selectedRecordId", ctx_r1.selectedRecordId)("cardTemplate", ctx_r1.cardTemplate)("hiddenFieldMatches", ctx_r1.hiddenFieldMatches)("filterText", ctx_r1.debouncedFilterText);
223
+ i0.ɵɵadvance();
224
+ i0.ɵɵconditional(ctx_r1.hasDateFields && ctx_r1.effectiveViewMode === "timeline" ? 2 : -1);
145
225
  } }
146
226
  function EntityViewerComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
147
- const _r7 = i0.ɵɵgetCurrentView();
148
- i0.ɵɵelementStart(0, "mj-pagination", 25);
149
- i0.ɵɵlistener("loadMore", function EntityViewerComponent_Conditional_7_Template_mj_pagination_loadMore_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onLoadMore()); });
227
+ const _r11 = i0.ɵɵgetCurrentView();
228
+ i0.ɵɵelementStart(0, "mj-pagination", 37);
229
+ i0.ɵɵlistener("loadMore", function EntityViewerComponent_Conditional_7_Template_mj_pagination_loadMore_0_listener() { i0.ɵɵrestoreView(_r11); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onLoadMore()); });
150
230
  i0.ɵɵelementEnd();
151
231
  } if (rf & 2) {
152
232
  const ctx_r1 = i0.ɵɵnextContext();
@@ -239,6 +319,31 @@ export class EntityViewerComponent {
239
319
  * Controls column visibility, widths, order, and sort settings
240
320
  */
241
321
  gridState = null;
322
+ /**
323
+ * Timeline configuration state
324
+ * Controls which date field is used and segment grouping
325
+ */
326
+ get timelineConfig() {
327
+ return this._timelineConfig;
328
+ }
329
+ set timelineConfig(value) {
330
+ const prev = this._timelineConfig;
331
+ // Compare by value, not reference
332
+ const isEqual = (prev === null && value === null) ||
333
+ (prev !== null && value !== null &&
334
+ prev.dateFieldName === value.dateFieldName &&
335
+ prev.sortOrder === value.sortOrder &&
336
+ prev.orientation === value.orientation &&
337
+ prev.segmentGrouping === value.segmentGrouping);
338
+ if (!isEqual) {
339
+ this._timelineConfig = value;
340
+ if (value && this.entity) {
341
+ this.configureTimeline();
342
+ this.cdr.markForCheck();
343
+ }
344
+ }
345
+ }
346
+ _timelineConfig = null;
242
347
  // ========================================
243
348
  // OUTPUTS
244
349
  // ========================================
@@ -274,6 +379,10 @@ export class EntityViewerComponent {
274
379
  * Emitted when grid state changes (column resize, reorder, etc.)
275
380
  */
276
381
  gridStateChanged = new EventEmitter();
382
+ /**
383
+ * Emitted when timeline configuration changes (date field, grouping, etc.)
384
+ */
385
+ timelineConfigChange = new EventEmitter();
277
386
  // ========================================
278
387
  // INTERNAL STATE
279
388
  // ========================================
@@ -297,6 +406,39 @@ export class EntityViewerComponent {
297
406
  hasMore: false,
298
407
  isLoading: false
299
408
  };
409
+ // ========================================
410
+ // TIMELINE STATE
411
+ // ========================================
412
+ /** Whether the current entity has date fields available for timeline view */
413
+ hasDateFields = false;
414
+ /** Available date fields from the entity (sorted by priority) */
415
+ availableDateFields = [];
416
+ /** Timeline groups configuration for the timeline component */
417
+ get timelineGroups() {
418
+ return this._timelineGroups;
419
+ }
420
+ set timelineGroups(value) {
421
+ const prev = this._timelineGroups;
422
+ this._timelineGroups = value;
423
+ // Detect meaningful changes to trigger refresh in child timeline component
424
+ const hasChanged = prev !== value ||
425
+ (prev.length > 0 && value.length > 0 &&
426
+ (prev[0].EntityObjects !== value[0]?.EntityObjects ||
427
+ prev[0].DateFieldName !== value[0]?.DateFieldName));
428
+ if (hasChanged) {
429
+ // Force change detection to propagate to child timeline component
430
+ this.cdr.markForCheck();
431
+ }
432
+ }
433
+ _timelineGroups = [];
434
+ /** Timeline sort order */
435
+ timelineSortOrder = 'desc';
436
+ /** Timeline segment grouping */
437
+ timelineSegmentGrouping = 'month';
438
+ /** Timeline orientation (vertical or horizontal) */
439
+ timelineOrientation = 'vertical';
440
+ /** Currently selected date field for timeline */
441
+ selectedTimelineDateField = null;
300
442
  destroy$ = new Subject();
301
443
  filterInput$ = new Subject();
302
444
  /** Track if this is the first load (vs. load more) */
@@ -319,6 +461,27 @@ export class EntityViewerComponent {
319
461
  get effectiveFilterText() {
320
462
  return this.filterText ?? this.internalFilterText;
321
463
  }
464
+ /**
465
+ * Get the raw ID value from selectedRecordId for timeline selection.
466
+ * The selectedRecordId is in composite key format (e.g., "ID|abc-123" or "ID=abc-123"),
467
+ * but the timeline stores just the raw ID value.
468
+ */
469
+ get timelineSelectedEventId() {
470
+ if (!this.selectedRecordId)
471
+ return null;
472
+ // Handle "ID|value" format (pipe separator)
473
+ if (this.selectedRecordId.includes('|')) {
474
+ const parts = this.selectedRecordId.split('|');
475
+ return parts.length > 1 ? parts[1] : this.selectedRecordId;
476
+ }
477
+ // Handle "ID=value" format (equals separator)
478
+ if (this.selectedRecordId.includes('=')) {
479
+ const parts = this.selectedRecordId.split('=');
480
+ return parts.length > 1 ? parts[1] : this.selectedRecordId;
481
+ }
482
+ // Return as-is if no separator found
483
+ return this.selectedRecordId;
484
+ }
322
485
  /**
323
486
  * Get the effective sort state (external or internal)
324
487
  */
@@ -484,6 +647,8 @@ export class EntityViewerComponent {
484
647
  this.applyConfig();
485
648
  }
486
649
  if (changes['entity']) {
650
+ // Detect date fields for timeline support
651
+ this.detectDateFields();
487
652
  if (this.entity && !this.records) {
488
653
  // Reset state for new entity - synchronously clear all data and force change detection
489
654
  // before starting the async load to prevent stale data display
@@ -503,7 +668,10 @@ export class EntityViewerComponent {
503
668
  this.internalRecords = this.records;
504
669
  this.totalRecordCount = this.records.length;
505
670
  this.filteredRecordCount = this.records.length;
671
+ // Update timeline with new records
672
+ this.updateTimelineGroups();
506
673
  }
674
+ // Timeline config is now handled by setter - no ngOnChanges handling needed
507
675
  // Handle external filter text changes (from parent component)
508
676
  if (changes['filterText']) {
509
677
  const newFilter = this.filterText ?? '';
@@ -693,6 +861,8 @@ export class EntityViewerComponent {
693
861
  filteredCount: this.internalRecords.length,
694
862
  totalCount: result.TotalRowCount
695
863
  });
864
+ // Update timeline groups with new data
865
+ this.updateTimelineGroups();
696
866
  }
697
867
  else {
698
868
  console.error('Failed to load records:', result.ErrorMessage);
@@ -816,12 +986,256 @@ export class EntityViewerComponent {
816
986
  onLoadMore() {
817
987
  this.loadMore();
818
988
  }
989
+ // ========================================
990
+ // TIMELINE METHODS
991
+ // ========================================
992
+ /**
993
+ * Handle timeline event click - emit as record selection
994
+ */
995
+ onTimelineEventClick(event) {
996
+ const record = event.event.entity;
997
+ if (record && this.entity) {
998
+ this.recordSelected.emit({
999
+ record,
1000
+ entity: this.entity,
1001
+ compositeKey: record.PrimaryKey
1002
+ });
1003
+ }
1004
+ }
1005
+ /**
1006
+ * Toggle timeline orientation between vertical and horizontal
1007
+ */
1008
+ toggleTimelineOrientation() {
1009
+ this.timelineOrientation = this.timelineOrientation === 'vertical' ? 'horizontal' : 'vertical';
1010
+ // Emit config change so parent can persist the preference
1011
+ this.emitTimelineConfigChange();
1012
+ this.cdr.detectChanges();
1013
+ }
1014
+ /**
1015
+ * Toggle timeline sort order between newest first (desc) and oldest first (asc)
1016
+ */
1017
+ toggleTimelineSortOrder() {
1018
+ this.timelineSortOrder = this.timelineSortOrder === 'desc' ? 'asc' : 'desc';
1019
+ // Emit config change so parent can persist the preference
1020
+ this.emitTimelineConfigChange();
1021
+ this.cdr.detectChanges();
1022
+ }
1023
+ /**
1024
+ * Change the date field used for the timeline
1025
+ */
1026
+ setTimelineDateField(fieldName) {
1027
+ if (this.availableDateFields.some(f => f.Name === fieldName)) {
1028
+ this.selectedTimelineDateField = fieldName;
1029
+ this.updateTimelineGroups();
1030
+ this.emitTimelineConfigChange();
1031
+ this.cdr.detectChanges();
1032
+ }
1033
+ }
1034
+ /**
1035
+ * Get the display name of the currently selected timeline date field
1036
+ */
1037
+ get selectedDateFieldDisplayName() {
1038
+ if (!this.selectedTimelineDateField)
1039
+ return '';
1040
+ const field = this.availableDateFields.find(f => f.Name === this.selectedTimelineDateField);
1041
+ return field?.DisplayNameOrName || this.selectedTimelineDateField;
1042
+ }
1043
+ /**
1044
+ * Emit the current timeline configuration for persistence
1045
+ */
1046
+ emitTimelineConfigChange() {
1047
+ if (this.selectedTimelineDateField) {
1048
+ this.timelineConfigChange.emit({
1049
+ dateFieldName: this.selectedTimelineDateField,
1050
+ sortOrder: this.timelineSortOrder,
1051
+ segmentGrouping: this.timelineSegmentGrouping,
1052
+ orientation: this.timelineOrientation
1053
+ });
1054
+ }
1055
+ }
1056
+ /**
1057
+ * Detect and configure timeline based on entity's date fields
1058
+ * Called when entity changes
1059
+ */
1060
+ detectDateFields() {
1061
+ if (!this.entity) {
1062
+ this.hasDateFields = false;
1063
+ this.availableDateFields = [];
1064
+ this.timelineGroups = [];
1065
+ this.fallbackFromTimelineIfNeeded();
1066
+ return;
1067
+ }
1068
+ // Find all date fields - include __mj_CreatedAt and __mj_UpdatedAt as they're useful for timelines
1069
+ const dateFields = this.entity.Fields.filter(f => f.TSType === EntityFieldTSType.Date);
1070
+ if (dateFields.length === 0) {
1071
+ this.hasDateFields = false;
1072
+ this.availableDateFields = [];
1073
+ this.timelineGroups = [];
1074
+ this.fallbackFromTimelineIfNeeded();
1075
+ return;
1076
+ }
1077
+ // Sort by priority: DefaultInView date fields first (by Sequence), then others (by Sequence)
1078
+ this.availableDateFields = this.sortDateFieldsByPriority(dateFields);
1079
+ this.hasDateFields = true;
1080
+ // Configure timeline with the best date field
1081
+ this.configureTimeline();
1082
+ }
1083
+ /**
1084
+ * If currently on timeline view but timeline is no longer available,
1085
+ * fall back to grid view
1086
+ */
1087
+ fallbackFromTimelineIfNeeded() {
1088
+ if (this.effectiveViewMode === 'timeline' && !this.hasDateFields) {
1089
+ this.setViewMode('grid');
1090
+ }
1091
+ }
1092
+ /**
1093
+ * Sort date fields by priority:
1094
+ * 1. DefaultInView=true fields, sorted by Sequence (lowest first)
1095
+ * 2. Other date fields, sorted by Sequence (lowest first)
1096
+ */
1097
+ sortDateFieldsByPriority(dateFields) {
1098
+ const defaultInView = dateFields.filter(f => f.DefaultInView).sort((a, b) => a.Sequence - b.Sequence);
1099
+ const others = dateFields.filter(f => !f.DefaultInView).sort((a, b) => a.Sequence - b.Sequence);
1100
+ return [...defaultInView, ...others];
1101
+ }
1102
+ /**
1103
+ * Configure the timeline with the current date field and records
1104
+ */
1105
+ configureTimeline() {
1106
+ if (!this.entity || !this.hasDateFields || this.availableDateFields.length === 0) {
1107
+ this.timelineGroups = [];
1108
+ return;
1109
+ }
1110
+ // Determine which date field to use
1111
+ const dateFieldName = this.getEffectiveTimelineDateField();
1112
+ this.selectedTimelineDateField = dateFieldName;
1113
+ // Apply timeline config if provided
1114
+ if (this.timelineConfig) {
1115
+ this.timelineSortOrder = (this.timelineConfig.sortOrder || 'desc');
1116
+ this.timelineSegmentGrouping = (this.timelineConfig.segmentGrouping || 'month');
1117
+ this.timelineOrientation = this.timelineConfig.orientation || 'vertical';
1118
+ }
1119
+ // Create a timeline group for the current entity's data
1120
+ this.updateTimelineGroups();
1121
+ }
1122
+ /**
1123
+ * Get the effective date field to use for timeline
1124
+ * Priority: timelineConfig > first available date field
1125
+ */
1126
+ getEffectiveTimelineDateField() {
1127
+ // If we have a config with a specific date field, use it if valid
1128
+ if (this.timelineConfig?.dateFieldName) {
1129
+ const configField = this.availableDateFields.find(f => f.Name === this.timelineConfig.dateFieldName);
1130
+ if (configField) {
1131
+ return configField.Name;
1132
+ }
1133
+ }
1134
+ // Otherwise use the first available date field (already sorted by priority)
1135
+ return this.availableDateFields[0].Name;
1136
+ }
1137
+ /**
1138
+ * Update timeline groups with current records
1139
+ * Called when records change
1140
+ */
1141
+ updateTimelineGroups() {
1142
+ if (!this.entity || !this.selectedTimelineDateField) {
1143
+ this.timelineGroups = [];
1144
+ return;
1145
+ }
1146
+ // Find title field - prefer NameField, then first string field with DefaultInView
1147
+ const titleField = this.findTitleField();
1148
+ // Create a single group for the current data
1149
+ const group = new TimelineGroup();
1150
+ group.DataSourceType = 'array';
1151
+ group.EntityObjects = this.filteredRecords;
1152
+ group.TitleFieldName = titleField;
1153
+ group.DateFieldName = this.selectedTimelineDateField;
1154
+ group.IdFieldName = 'ID';
1155
+ group.GroupLabel = this.entity.Name;
1156
+ // Find a suitable description field
1157
+ const descField = this.findDescriptionField();
1158
+ if (descField) {
1159
+ group.DescriptionFieldName = descField;
1160
+ }
1161
+ // Find a suitable subtitle field
1162
+ const subtitleField = this.findSubtitleField(titleField);
1163
+ if (subtitleField) {
1164
+ group.SubtitleFieldName = subtitleField;
1165
+ }
1166
+ // Configure card display
1167
+ group.CardConfig = {
1168
+ collapsible: true,
1169
+ defaultExpanded: false,
1170
+ showDate: true,
1171
+ dateFormat: 'MMM d, yyyy h:mm a'
1172
+ };
1173
+ this.timelineGroups = [group];
1174
+ }
1175
+ /**
1176
+ * Find the best field to use as the title
1177
+ */
1178
+ findTitleField() {
1179
+ if (!this.entity)
1180
+ return 'ID';
1181
+ // Prefer the entity's NameField
1182
+ if (this.entity.NameField) {
1183
+ return this.entity.NameField.Name;
1184
+ }
1185
+ // Look for common name patterns in DefaultInView string fields
1186
+ const stringFields = this.entity.Fields.filter(f => f.TSType === EntityFieldTSType.String && f.DefaultInView && !f.Name.startsWith('__mj_')).sort((a, b) => a.Sequence - b.Sequence);
1187
+ const namePatterns = ['name', 'title', 'subject', 'label'];
1188
+ for (const pattern of namePatterns) {
1189
+ const match = stringFields.find(f => f.Name.toLowerCase().includes(pattern));
1190
+ if (match)
1191
+ return match.Name;
1192
+ }
1193
+ // Fall back to first string field
1194
+ return stringFields.length > 0 ? stringFields[0].Name : 'ID';
1195
+ }
1196
+ /**
1197
+ * Find a suitable description field
1198
+ */
1199
+ findDescriptionField() {
1200
+ if (!this.entity)
1201
+ return null;
1202
+ // Look for common description patterns
1203
+ const descPatterns = ['description', 'notes', 'summary', 'content', 'body', 'details'];
1204
+ const textFields = this.entity.Fields.filter(f => (f.TSType === EntityFieldTSType.String) && !f.Name.startsWith('__mj_'));
1205
+ for (const pattern of descPatterns) {
1206
+ const match = textFields.find(f => f.Name.toLowerCase().includes(pattern));
1207
+ if (match)
1208
+ return match.Name;
1209
+ }
1210
+ return null;
1211
+ }
1212
+ /**
1213
+ * Find a suitable subtitle field (different from title)
1214
+ */
1215
+ findSubtitleField(excludeField) {
1216
+ if (!this.entity)
1217
+ return null;
1218
+ // Look for status, type, category, or other short classification fields
1219
+ const patterns = ['status', 'type', 'category', 'state', 'priority'];
1220
+ const fields = this.entity.Fields.filter(f => f.TSType === EntityFieldTSType.String &&
1221
+ f.DefaultInView &&
1222
+ f.Name !== excludeField &&
1223
+ !f.Name.startsWith('__mj_')).sort((a, b) => a.Sequence - b.Sequence);
1224
+ for (const pattern of patterns) {
1225
+ const match = fields.find(f => f.Name.toLowerCase().includes(pattern));
1226
+ if (match)
1227
+ return match.Name;
1228
+ }
1229
+ // Use the first string field that's not the title field
1230
+ const firstOther = fields.find(f => f.Name !== excludeField);
1231
+ return firstOther?.Name || null;
1232
+ }
819
1233
  static ɵfac = function EntityViewerComponent_Factory(t) { return new (t || EntityViewerComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
820
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityViewerComponent, selectors: [["mj-entity-viewer"]], 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" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened", dataLoaded: "dataLoaded", viewModeChange: "viewModeChange", filterTextChange: "filterTextChange", filteredCountChanged: "filteredCountChanged", sortChanged: "sortChanged", gridStateChanged: "gridStateChanged" }, features: [i0.ɵɵNgOnChangesFeature], decls: 8, vars: 5, consts: [[1, "entity-viewer-container"], [1, "viewer-header"], [1, "viewer-content"], [1, "loading-container"], [1, "empty-state"], [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"], ["size", "medium", 3, "text"], [1, "fa-solid", "fa-database"], [1, "fa-solid", "fa-inbox"], [3, "entity", "records", "selectedRecordId", "columns", "height", "filterText", "sortState", "serverSideSorting", "gridState"], [3, "entity", "records", "selectedRecordId", "cardTemplate", "hiddenFieldMatches", "filterText"], [3, "recordSelected", "recordOpened", "sortChanged", "gridStateChanged", "entity", "records", "selectedRecordId", "columns", "height", "filterText", "sortState", "serverSideSorting", "gridState"], [3, "recordSelected", "recordOpened", "entity", "records", "selectedRecordId", "cardTemplate", "hiddenFieldMatches", "filterText"], [3, "loadMore", "pagination", "loadedRecordCount"]], template: function EntityViewerComponent_Template(rf, ctx) { if (rf & 1) {
1234
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EntityViewerComponent, selectors: [["mj-entity-viewer"]], 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" }, outputs: { recordSelected: "recordSelected", recordOpened: "recordOpened", dataLoaded: "dataLoaded", viewModeChange: "viewModeChange", filterTextChange: "filterTextChange", filteredCountChanged: "filteredCountChanged", sortChanged: "sortChanged", gridStateChanged: "gridStateChanged", timelineConfigChange: "timelineConfigChange" }, features: [i0.ɵɵNgOnChangesFeature], decls: 8, vars: 5, consts: [[1, "entity-viewer-container"], [1, "viewer-header"], [1, "viewer-content"], [1, "loading-container"], [1, "empty-state"], [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"], ["size", "medium", 3, "text"], [1, "fa-solid", "fa-database"], [1, "fa-solid", "fa-inbox"], [3, "recordSelected", "recordOpened", "sortChanged", "gridStateChanged", "hidden", "entity", "records", "selectedRecordId", "columns", "height", "filterText", "sortState", "serverSideSorting", "gridState"], [3, "recordSelected", "recordOpened", "hidden", "entity", "records", "selectedRecordId", "cardTemplate", "hiddenFieldMatches", "filterText"], [3, "groups", "orientation", "layout", "sortOrder", "segmentGrouping", "segmentsCollapsible", "segmentsDefaultExpanded", "selectedEventId"], [3, "afterEventClick", "groups", "orientation", "layout", "sortOrder", "segmentGrouping", "segmentsCollapsible", "segmentsDefaultExpanded", "selectedEventId"], [3, "loadMore", "pagination", "loadedRecordCount"]], template: function EntityViewerComponent_Template(rf, ctx) { if (rf & 1) {
821
1235
  i0.ɵɵelementStart(0, "div", 0);
822
- i0.ɵɵtemplate(1, EntityViewerComponent_Conditional_1_Template, 4, 3, "div", 1);
1236
+ i0.ɵɵtemplate(1, EntityViewerComponent_Conditional_1_Template, 5, 4, "div", 1);
823
1237
  i0.ɵɵelementStart(2, "div", 2);
824
- i0.ɵɵtemplate(3, EntityViewerComponent_Conditional_3_Template, 2, 1, "div", 3)(4, EntityViewerComponent_Conditional_4_Template, 4, 0, "div", 4)(5, EntityViewerComponent_Conditional_5_Template, 4, 1, "div", 4)(6, EntityViewerComponent_Conditional_6_Template, 2, 1);
1238
+ i0.ɵɵtemplate(3, EntityViewerComponent_Conditional_3_Template, 2, 1, "div", 3)(4, EntityViewerComponent_Conditional_4_Template, 4, 0, "div", 4)(5, EntityViewerComponent_Conditional_5_Template, 4, 1, "div", 4)(6, EntityViewerComponent_Conditional_6_Template, 3, 18);
825
1239
  i0.ɵɵelementEnd();
826
1240
  i0.ɵɵtemplate(7, EntityViewerComponent_Conditional_7_Template, 1, 2, "mj-pagination", 5);
827
1241
  i0.ɵɵelementEnd();
@@ -833,13 +1247,13 @@ export class EntityViewerComponent {
833
1247
  i0.ɵɵconditional(ctx.isLoading && ctx.filteredRecords.length === 0 ? 3 : !ctx.entity ? 4 : ctx.filteredRecords.length === 0 && !ctx.isLoading ? 5 : 6);
834
1248
  i0.ɵɵadvance(4);
835
1249
  i0.ɵɵconditional(ctx.effectiveConfig.showPagination && ctx.entity && (ctx.pagination.hasMore || ctx.pagination.totalRecords > ctx.effectiveConfig.pageSize) ? 7 : -1);
836
- } }, dependencies: [i1.LoadingComponent, i2.EntityGridComponent, i3.EntityCardsComponent, i4.PaginationComponent, i5.DecimalPipe], styles: [".entity-viewer-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n background: #fafafa;\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.viewer-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n position: relative;\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.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}"] });
1250
+ } }, dependencies: [i1.NgSelectOption, i1.ɵNgSelectMultipleOption, i2.LoadingComponent, i3.TimelineComponent, i4.EntityGridComponent, i5.EntityCardsComponent, i6.PaginationComponent, i7.DecimalPipe], styles: [".entity-viewer-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n width: 100%;\n background: #fafafa;\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.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-grid[hidden][_ngcontent-%COMP%], \nmj-entity-cards[hidden][_ngcontent-%COMP%], \nmj-timeline[hidden][_ngcontent-%COMP%] {\n display: none !important;\n}\n\n\n\nmj-entity-grid[_ngcontent-%COMP%]:not([hidden]), \nmj-entity-cards[_ngcontent-%COMP%]:not([hidden]), \nmj-timeline[_ngcontent-%COMP%]:not([hidden]) {\n display: block;\n height: 100%;\n width: 100%;\n}"] });
837
1251
  }
838
1252
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EntityViewerComponent, [{
839
1253
  type: Component,
840
1254
  args: [{ selector: 'mj-entity-viewer', host: {
841
1255
  'style': 'display: block; height: 100%;'
842
- }, 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 </div>\n }\n </div>\n }\n\n <!-- Content -->\n <div class=\"viewer-content\">\n @if (isLoading && filteredRecords.length === 0) {\n <!-- Initial loading state -->\n <div class=\"loading-container\">\n <mj-loading [text]=\"loadingMessage\" size=\"medium\"></mj-loading>\n </div>\n } @else if (!entity) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-database\"></i>\n <p>Select an entity to view records</p>\n </div>\n } @else if (filteredRecords.length === 0 && !isLoading) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>{{ debouncedFilterText ? 'No matching records' : 'No records found' }}</p>\n </div>\n } @else {\n @if (effectiveViewMode === 'grid') {\n <mj-entity-grid\n [entity]=\"entity\"\n [records]=\"filteredRecords\"\n [selectedRecordId]=\"selectedRecordId\"\n [columns]=\"gridColumns\"\n [height]=\"'100%'\"\n [filterText]=\"debouncedFilterText\"\n [sortState]=\"effectiveSortState\"\n [serverSideSorting]=\"effectiveConfig.serverSideSorting\"\n [gridState]=\"gridState\"\n (recordSelected)=\"onRecordSelected($event)\"\n (recordOpened)=\"onRecordOpened($event)\"\n (sortChanged)=\"onSortChanged($event)\"\n (gridStateChanged)=\"onGridStateChanged($event)\">\n </mj-entity-grid>\n } @else {\n <mj-entity-cards\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 }\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 background: #fafafa;\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/* Content */\n.viewer-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n position: relative;\n}\n\n/* Loading State */\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\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"] }]
1256
+ }, 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 @if (isLoading && filteredRecords.length === 0) {\n <!-- Initial loading state -->\n <div class=\"loading-container\">\n <mj-loading [text]=\"loadingMessage\" size=\"medium\"></mj-loading>\n </div>\n } @else if (!entity) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-database\"></i>\n <p>Select an entity to view records</p>\n </div>\n } @else if (filteredRecords.length === 0 && !isLoading) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-inbox\"></i>\n <p>{{ debouncedFilterText ? 'No matching records' : 'No records found' }}</p>\n </div>\n } @else {\n <!-- Grid View - always rendered but hidden when not active to preserve state -->\n <mj-entity-grid\n [hidden]=\"effectiveViewMode !== 'grid'\"\n [entity]=\"entity\"\n [records]=\"filteredRecords\"\n [selectedRecordId]=\"selectedRecordId\"\n [columns]=\"gridColumns\"\n [height]=\"'100%'\"\n [filterText]=\"debouncedFilterText\"\n [sortState]=\"effectiveSortState\"\n [serverSideSorting]=\"effectiveConfig.serverSideSorting\"\n [gridState]=\"gridState\"\n (recordSelected)=\"onRecordSelected($event)\"\n (recordOpened)=\"onRecordOpened($event)\"\n (sortChanged)=\"onSortChanged($event)\"\n (gridStateChanged)=\"onGridStateChanged($event)\">\n </mj-entity-grid>\n\n <!-- Cards View - always rendered but hidden when not active to preserve state -->\n <mj-entity-cards\n [hidden]=\"effectiveViewMode !== 'cards'\"\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 - only created when timeline mode is active -->\n @if (hasDateFields && effectiveViewMode === 'timeline') {\n <mj-timeline\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 }\n }\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 background: #fafafa;\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 */\n.loading-container {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\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-grid[hidden],\nmj-entity-cards[hidden],\nmj-timeline[hidden] {\n display: none !important;\n}\n\n/* Visible view components should fill available space */\nmj-entity-grid:not([hidden]),\nmj-entity-cards:not([hidden]),\nmj-timeline:not([hidden]) {\n display: block;\n height: 100%;\n width: 100%;\n}\n"] }]
843
1257
  }], () => [{ type: i0.ChangeDetectorRef }], { entity: [{
844
1258
  type: Input
845
1259
  }], records: [{
@@ -862,6 +1276,8 @@ export class EntityViewerComponent {
862
1276
  type: Input
863
1277
  }], gridState: [{
864
1278
  type: Input
1279
+ }], timelineConfig: [{
1280
+ type: Input
865
1281
  }], recordSelected: [{
866
1282
  type: Output
867
1283
  }], recordOpened: [{
@@ -878,6 +1294,8 @@ export class EntityViewerComponent {
878
1294
  type: Output
879
1295
  }], gridStateChanged: [{
880
1296
  type: Output
1297
+ }], timelineConfigChange: [{
1298
+ type: Output
881
1299
  }] }); })();
882
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityViewerComponent, { className: "EntityViewerComponent", filePath: "src/lib/entity-viewer/entity-viewer.component.ts", lineNumber: 65 }); })();
1300
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityViewerComponent, { className: "EntityViewerComponent", filePath: "src/lib/entity-viewer/entity-viewer.component.ts", lineNumber: 69 }); })();
883
1301
  //# sourceMappingURL=entity-viewer.component.js.map