@memberjunction/ng-entity-viewer 6.1.4 → 6.2.0-edge.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts +18 -2
- package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts.map +1 -1
- package/dist/lib/aggregate-panel/aggregate-panel.component.js +47 -18
- package/dist/lib/aggregate-panel/aggregate-panel.component.js.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts +22 -0
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +57 -13
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.d.ts +68 -5
- package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.js +213 -73
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +156 -14
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.js +432 -138
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
- package/dist/lib/entity-data-grid/events/grid-events.js +67 -67
- package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +112 -8
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +255 -69
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +11 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +45 -8
- package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
- package/dist/lib/pill/pill.component.d.ts +13 -3
- package/dist/lib/pill/pill.component.d.ts.map +1 -1
- package/dist/lib/pill/pill.component.js +34 -10
- package/dist/lib/pill/pill.component.js.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts +29 -3
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts.map +1 -1
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +173 -114
- package/dist/lib/quick-save-dialog/quick-save-dialog.component.js.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts +4 -0
- package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -1
- package/dist/lib/recycle-bin/recycle-bin.component.js +10 -2
- package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -1
- package/dist/lib/utils/aggregate-field.util.d.ts +21 -0
- package/dist/lib/utils/aggregate-field.util.d.ts.map +1 -0
- package/dist/lib/utils/aggregate-field.util.js +40 -0
- package/dist/lib/utils/aggregate-field.util.js.map +1 -0
- package/dist/lib/utils/highlight.util.d.ts +6 -0
- package/dist/lib/utils/highlight.util.d.ts.map +1 -1
- package/dist/lib/utils/highlight.util.js +23 -11
- package/dist/lib/utils/highlight.util.js.map +1 -1
- package/dist/lib/utils/record.util.d.ts +10 -0
- package/dist/lib/utils/record.util.d.ts.map +1 -1
- package/dist/lib/utils/record.util.js +20 -4
- package/dist/lib/utils/record.util.js.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts +22 -2
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.js +54 -14
- package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
- package/dist/lib/view-selector/view-selector.component.js +2 -2
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts +15 -3
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js +33 -5
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +160 -5
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js +576 -36
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts +16 -2
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.js +37 -9
- package/dist/lib/view-types/renderers/map-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/view-type.contracts.js.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.d.ts +235 -24
- package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.js +600 -174
- package/dist/lib/view-workspace/view-workspace.component.js.map +1 -1
- package/dist/module.d.ts +5 -4
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +8 -1
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +21 -20
|
@@ -3,11 +3,12 @@ import { trigger, transition, style, animate } from '@angular/animations';
|
|
|
3
3
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
4
4
|
import { Subject } from 'rxjs';
|
|
5
5
|
import { debounceTime, takeUntil } from 'rxjs/operators';
|
|
6
|
-
import { LogError, RunView, RunViewParams, CoerceImageSrc, ParseCssHexColor, CompositeKey } from '@memberjunction/core';
|
|
6
|
+
import { LogError, RunView, RunViewParams, CoerceImageSrc, ParseCssHexColor, CompositeKey, IsDateOnlySQLType, FormatDateOnly, EntityFieldTSType } from '@memberjunction/core';
|
|
7
7
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
8
8
|
import { EntityActionEngineBase } from '@memberjunction/actions-base';
|
|
9
9
|
import { CommunicationEngineBase } from '@memberjunction/communication-types';
|
|
10
|
-
import {
|
|
10
|
+
import { BuildPkString, CanonicalizeColumnFields, ComputeFieldsList } from '../utils/record.util';
|
|
11
|
+
import { AggregateField } from '../utils/aggregate-field.util';
|
|
11
12
|
import { ViewInfo, UserViewEngine, UserInfoEngine } from '@memberjunction/core-entities';
|
|
12
13
|
import { ModuleRegistry, AllCommunityModule, themeAlpine } from 'ag-grid-community';
|
|
13
14
|
// Use the existing HighlightUtil from entity-viewer package
|
|
@@ -535,7 +536,7 @@ function EntityDataGridComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
|
535
536
|
i0.ɵɵelement(0, "mj-empty-state", 6);
|
|
536
537
|
} if (rf & 2) {
|
|
537
538
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
538
|
-
i0.ɵɵproperty("Size", ctx_r1.EmptyStateSize);
|
|
539
|
+
i0.ɵɵproperty("Size", ctx_r1.EmptyStateSize)("Title", ctx_r1.EntityInfo ? "No " + ctx_r1.EntityInfo.DisplayNamePlural + " to display" : "No data to display");
|
|
539
540
|
} }
|
|
540
541
|
function EntityDataGridComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
541
542
|
const _r30 = i0.ɵɵgetCurrentView();
|
|
@@ -795,9 +796,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
795
796
|
* Convenience accessor for the resolved entity name. Returns null when
|
|
796
797
|
* the entity hasn't been resolved yet (e.g., before data loads).
|
|
797
798
|
*/
|
|
798
|
-
get
|
|
799
|
+
get EntityInfoName() {
|
|
799
800
|
return this._entityInfo?.Name ?? null;
|
|
800
801
|
}
|
|
802
|
+
/** @deprecated Use {@link EntityInfoName}. */
|
|
803
|
+
get entityInfoName() {
|
|
804
|
+
return this.EntityInfoName;
|
|
805
|
+
}
|
|
801
806
|
/**
|
|
802
807
|
* Current page number for the shared pager (1-based).
|
|
803
808
|
* Set by parent when using external data with server-side paging.
|
|
@@ -1150,9 +1155,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1150
1155
|
/**
|
|
1151
1156
|
* Get the effective visual config (user config merged with defaults)
|
|
1152
1157
|
*/
|
|
1153
|
-
get
|
|
1158
|
+
get EffectiveVisualConfig() {
|
|
1154
1159
|
return { ...DEFAULT_VISUAL_CONFIG, ...this._visualConfig };
|
|
1155
1160
|
}
|
|
1161
|
+
/** @deprecated Use {@link EffectiveVisualConfig}. */
|
|
1162
|
+
get effectiveVisualConfig() {
|
|
1163
|
+
return this.EffectiveVisualConfig;
|
|
1164
|
+
}
|
|
1156
1165
|
// ========================================
|
|
1157
1166
|
// Toolbar Inputs
|
|
1158
1167
|
// ========================================
|
|
@@ -1481,7 +1490,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1481
1490
|
this._entityActionsAutoLoaded = false;
|
|
1482
1491
|
}
|
|
1483
1492
|
}
|
|
1484
|
-
/**
|
|
1493
|
+
/** The EntityAction engine for this grid's provider, so a multi-provider client reads the right server's actions. */
|
|
1485
1494
|
entityActionEngine() {
|
|
1486
1495
|
return EntityActionEngineBase.GetProviderInstance(this.ProviderToUse, EntityActionEngineBase);
|
|
1487
1496
|
}
|
|
@@ -1540,14 +1549,14 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1540
1549
|
* Returns the effective aggregates config, preferring _aggregatesConfig but falling back to _gridState.aggregates.
|
|
1541
1550
|
* This ensures aggregates work regardless of whether they came from explicit config or from view's GridState.
|
|
1542
1551
|
*/
|
|
1543
|
-
get
|
|
1552
|
+
get effectiveAggregatesConfig() {
|
|
1544
1553
|
return this._aggregatesConfig || this._gridState?.aggregates;
|
|
1545
1554
|
}
|
|
1546
1555
|
/**
|
|
1547
1556
|
* Returns enabled aggregates configured for card display.
|
|
1548
1557
|
*/
|
|
1549
1558
|
get CardAggregates() {
|
|
1550
|
-
const config = this.
|
|
1559
|
+
const config = this.effectiveAggregatesConfig;
|
|
1551
1560
|
if (!config?.expressions)
|
|
1552
1561
|
return [];
|
|
1553
1562
|
return config.expressions
|
|
@@ -1558,7 +1567,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1558
1567
|
* Returns enabled aggregates configured for column footer display.
|
|
1559
1568
|
*/
|
|
1560
1569
|
get ColumnAggregates() {
|
|
1561
|
-
const config = this.
|
|
1570
|
+
const config = this.effectiveAggregatesConfig;
|
|
1562
1571
|
if (!config?.expressions)
|
|
1563
1572
|
return [];
|
|
1564
1573
|
return config.expressions
|
|
@@ -1582,7 +1591,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1582
1591
|
/**
|
|
1583
1592
|
* Get formatted aggregate value for display
|
|
1584
1593
|
*/
|
|
1585
|
-
|
|
1594
|
+
GetAggregateValue(agg) {
|
|
1586
1595
|
const key = agg.id || agg.expression;
|
|
1587
1596
|
const value = this._aggregateValues.get(key);
|
|
1588
1597
|
if (value == null)
|
|
@@ -1594,11 +1603,37 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1594
1603
|
minimumFractionDigits: 0
|
|
1595
1604
|
});
|
|
1596
1605
|
}
|
|
1606
|
+
// A date aggregate reaches the browser as an ISO string: the server JSON-stringifies the value
|
|
1607
|
+
// and the client parses it back, which turns a Date into text. Resolve the column's type first
|
|
1608
|
+
// so a `date` column renders as its stored day whether the value is a Date or that string.
|
|
1609
|
+
if (typeof value !== 'boolean' && this.aggregateIsDateOnly(agg)) {
|
|
1610
|
+
return FormatDateOnly(value);
|
|
1611
|
+
}
|
|
1597
1612
|
if (value instanceof Date) {
|
|
1598
1613
|
return value.toLocaleDateString();
|
|
1599
1614
|
}
|
|
1615
|
+
// A timestamp aggregate arrives as the same ISO string. It names an instant, so it is rendered
|
|
1616
|
+
// in local time, as a Date instance would be — not printed as the wire text.
|
|
1617
|
+
if (typeof value === 'string' && AggregateField(agg, this._entityInfo)?.TSType === EntityFieldTSType.Date) {
|
|
1618
|
+
const date = new Date(value);
|
|
1619
|
+
if (!isNaN(date.getTime()))
|
|
1620
|
+
return date.toLocaleDateString();
|
|
1621
|
+
}
|
|
1600
1622
|
return String(value);
|
|
1601
1623
|
}
|
|
1624
|
+
/** @deprecated Use {@link GetAggregateValue}. */
|
|
1625
|
+
getAggregateValue(agg) {
|
|
1626
|
+
return this.GetAggregateValue(agg);
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Whether an aggregate summarises a date-only column. An aggregate carries no field metadata of
|
|
1630
|
+
* its own, so the column is read from a single-field expression such as `MIN(IntakeDate)` or
|
|
1631
|
+
* from the column the aggregate is pinned under. A `date` column is a calendar day and must not
|
|
1632
|
+
* be shifted into the reader's zone (MJ#4210).
|
|
1633
|
+
*/
|
|
1634
|
+
aggregateIsDateOnly(agg) {
|
|
1635
|
+
return IsDateOnlySQLType(AggregateField(agg, this._entityInfo)?.Type);
|
|
1636
|
+
}
|
|
1602
1637
|
// ========================================
|
|
1603
1638
|
// Event Outputs
|
|
1604
1639
|
// ========================================
|
|
@@ -1725,18 +1760,42 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1725
1760
|
// ========================================
|
|
1726
1761
|
// View Children
|
|
1727
1762
|
// ========================================
|
|
1728
|
-
|
|
1763
|
+
GridContainer;
|
|
1764
|
+
/** @deprecated Use {@link GridContainer}. */
|
|
1765
|
+
get gridContainer() {
|
|
1766
|
+
return this.GridContainer;
|
|
1767
|
+
}
|
|
1768
|
+
/** @deprecated Use {@link GridContainer}. */
|
|
1769
|
+
set gridContainer(value) {
|
|
1770
|
+
this.GridContainer = value;
|
|
1771
|
+
}
|
|
1729
1772
|
// ========================================
|
|
1730
1773
|
// AG Grid Properties
|
|
1731
1774
|
// ========================================
|
|
1732
1775
|
/** AG Grid column definitions */
|
|
1733
|
-
|
|
1776
|
+
AgColumnDefs = [];
|
|
1777
|
+
/** @deprecated Use {@link AgColumnDefs}. */
|
|
1778
|
+
get agColumnDefs() {
|
|
1779
|
+
return this.AgColumnDefs;
|
|
1780
|
+
}
|
|
1781
|
+
/** @deprecated Use {@link AgColumnDefs}. */
|
|
1782
|
+
set agColumnDefs(value) {
|
|
1783
|
+
this.AgColumnDefs = value;
|
|
1784
|
+
}
|
|
1734
1785
|
/** AG Grid row data */
|
|
1735
|
-
|
|
1786
|
+
RowData = [];
|
|
1787
|
+
/** @deprecated Use {@link RowData}. */
|
|
1788
|
+
get rowData() {
|
|
1789
|
+
return this.RowData;
|
|
1790
|
+
}
|
|
1791
|
+
/** @deprecated Use {@link RowData}. */
|
|
1792
|
+
set rowData(value) {
|
|
1793
|
+
this.RowData = value;
|
|
1794
|
+
}
|
|
1736
1795
|
/** AG Grid API reference */
|
|
1737
1796
|
gridApi = null;
|
|
1738
1797
|
/** AG Grid theme (v34+) with custom selection colors */
|
|
1739
|
-
|
|
1798
|
+
AgGridTheme = themeAlpine.withParams({
|
|
1740
1799
|
backgroundColor: 'var(--mj-bg-surface)',
|
|
1741
1800
|
foregroundColor: 'var(--mj-text-primary)',
|
|
1742
1801
|
textColor: 'var(--mj-text-primary)',
|
|
@@ -1754,23 +1813,47 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1754
1813
|
borderRadius: 'var(--mj-radius-sm)',
|
|
1755
1814
|
browserColorScheme: 'inherit',
|
|
1756
1815
|
});
|
|
1816
|
+
/** @deprecated Use {@link AgGridTheme}. */
|
|
1817
|
+
get agGridTheme() {
|
|
1818
|
+
return this.AgGridTheme;
|
|
1819
|
+
}
|
|
1820
|
+
/** @deprecated Use {@link AgGridTheme}. */
|
|
1821
|
+
set agGridTheme(value) {
|
|
1822
|
+
this.AgGridTheme = value;
|
|
1823
|
+
}
|
|
1757
1824
|
/** AG Grid row selection configuration */
|
|
1758
|
-
|
|
1825
|
+
AgRowSelection = { mode: 'singleRow' };
|
|
1826
|
+
/** @deprecated Use {@link AgRowSelection}. */
|
|
1827
|
+
get agRowSelection() {
|
|
1828
|
+
return this.AgRowSelection;
|
|
1829
|
+
}
|
|
1830
|
+
/** @deprecated Use {@link AgRowSelection}. */
|
|
1831
|
+
set agRowSelection(value) {
|
|
1832
|
+
this.AgRowSelection = value;
|
|
1833
|
+
}
|
|
1759
1834
|
/** Default column settings */
|
|
1760
|
-
|
|
1835
|
+
DefaultColDef = {
|
|
1761
1836
|
sortable: true,
|
|
1762
1837
|
filter: false,
|
|
1763
1838
|
resizable: true,
|
|
1764
1839
|
minWidth: 80
|
|
1765
1840
|
};
|
|
1841
|
+
/** @deprecated Use {@link DefaultColDef}. */
|
|
1842
|
+
get defaultColDef() {
|
|
1843
|
+
return this.DefaultColDef;
|
|
1844
|
+
}
|
|
1845
|
+
/** @deprecated Use {@link DefaultColDef}. */
|
|
1846
|
+
set defaultColDef(value) {
|
|
1847
|
+
this.DefaultColDef = value;
|
|
1848
|
+
}
|
|
1766
1849
|
/**
|
|
1767
1850
|
* Update defaultColDef when text wrapping setting changes
|
|
1768
1851
|
* Enables/disables auto row height and cell text wrapping
|
|
1769
1852
|
*/
|
|
1770
1853
|
updateDefaultColDefForWrapping() {
|
|
1771
1854
|
if (this._wrapText) {
|
|
1772
|
-
this.
|
|
1773
|
-
...this.
|
|
1855
|
+
this.DefaultColDef = {
|
|
1856
|
+
...this.DefaultColDef,
|
|
1774
1857
|
wrapText: true,
|
|
1775
1858
|
autoHeight: true,
|
|
1776
1859
|
cellClass: 'cell-wrap-text'
|
|
@@ -1778,21 +1861,37 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1778
1861
|
}
|
|
1779
1862
|
else {
|
|
1780
1863
|
// Remove wrapping properties
|
|
1781
|
-
const { wrapText, autoHeight, cellClass, ...rest } = this.
|
|
1782
|
-
this.
|
|
1864
|
+
const { wrapText, autoHeight, cellClass, ...rest } = this.DefaultColDef;
|
|
1865
|
+
this.DefaultColDef = rest;
|
|
1783
1866
|
}
|
|
1784
1867
|
// Refresh the grid to apply changes
|
|
1785
1868
|
if (this.gridApi) {
|
|
1786
|
-
this.gridApi.setGridOption('defaultColDef', this.
|
|
1869
|
+
this.gridApi.setGridOption('defaultColDef', this.DefaultColDef);
|
|
1787
1870
|
this.gridApi.refreshCells({ force: true });
|
|
1788
1871
|
}
|
|
1789
1872
|
}
|
|
1790
1873
|
/** Get row ID function for AG Grid */
|
|
1791
|
-
|
|
1874
|
+
GetRowId = (params) => params.data['__pk'];
|
|
1875
|
+
/** @deprecated Use {@link GetRowId}. */
|
|
1876
|
+
get getRowId() {
|
|
1877
|
+
return this.GetRowId;
|
|
1878
|
+
}
|
|
1879
|
+
/** @deprecated Use {@link GetRowId}. */
|
|
1880
|
+
set getRowId(value) {
|
|
1881
|
+
this.GetRowId = value;
|
|
1882
|
+
}
|
|
1792
1883
|
/** Suppress sort changed events during programmatic updates */
|
|
1793
1884
|
suppressSortEvents = false;
|
|
1794
1885
|
/** AG Grid options for infinite scroll mode */
|
|
1795
|
-
|
|
1886
|
+
GridOptions = {};
|
|
1887
|
+
/** @deprecated Use {@link GridOptions}. */
|
|
1888
|
+
get gridOptions() {
|
|
1889
|
+
return this.GridOptions;
|
|
1890
|
+
}
|
|
1891
|
+
/** @deprecated Use {@link GridOptions}. */
|
|
1892
|
+
set gridOptions(value) {
|
|
1893
|
+
this.GridOptions = value;
|
|
1894
|
+
}
|
|
1796
1895
|
/** Datasource for infinite scroll mode */
|
|
1797
1896
|
infiniteDatasource = null;
|
|
1798
1897
|
// ========================================
|
|
@@ -1847,7 +1946,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1847
1946
|
return true;
|
|
1848
1947
|
}
|
|
1849
1948
|
// Loading state
|
|
1850
|
-
|
|
1949
|
+
Loading = false;
|
|
1950
|
+
/** @deprecated Use {@link Loading}. */
|
|
1951
|
+
get loading() {
|
|
1952
|
+
return this.Loading;
|
|
1953
|
+
}
|
|
1954
|
+
/** @deprecated Use {@link Loading}. */
|
|
1955
|
+
set loading(value) {
|
|
1956
|
+
this.Loading = value;
|
|
1957
|
+
}
|
|
1851
1958
|
_errorMessage = '';
|
|
1852
1959
|
/**
|
|
1853
1960
|
* Raw technical error from the last failed load. Kept for callers and the
|
|
@@ -1872,7 +1979,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1872
1979
|
const friendly = 'The server may be busy or briefly unreachable — retrying usually fixes this.';
|
|
1873
1980
|
return this._errorMessage ? `${friendly} (Detail: ${this._errorMessage})` : friendly;
|
|
1874
1981
|
}
|
|
1875
|
-
totalRowCount = 0;
|
|
1982
|
+
totalRowCount = 0; // case-violation-ok-legacy-back-compat: the PascalCase name is already taken in this scope
|
|
1876
1983
|
_loadDataPromise = null;
|
|
1877
1984
|
// Cleanup
|
|
1878
1985
|
destroy$ = new Subject();
|
|
@@ -1907,17 +2014,45 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1907
2014
|
* For dynamic views, always true (persists to user settings).
|
|
1908
2015
|
* For saved views, depends on view ownership and permissions.
|
|
1909
2016
|
*/
|
|
1910
|
-
get
|
|
2017
|
+
get CanEditCurrentView() {
|
|
1911
2018
|
if (this.IsDynamicView) {
|
|
1912
2019
|
return true; // Dynamic views persist to user settings
|
|
1913
2020
|
}
|
|
1914
2021
|
return this._viewEntity?.UserCanEdit ?? false;
|
|
1915
2022
|
}
|
|
2023
|
+
/** @deprecated Use {@link CanEditCurrentView}. */
|
|
2024
|
+
get canEditCurrentView() {
|
|
2025
|
+
return this.CanEditCurrentView;
|
|
2026
|
+
}
|
|
1916
2027
|
// Overflow menu state
|
|
1917
|
-
|
|
2028
|
+
ShowOverflowMenu = false;
|
|
2029
|
+
/** @deprecated Use {@link ShowOverflowMenu}. */
|
|
2030
|
+
get showOverflowMenu() {
|
|
2031
|
+
return this.ShowOverflowMenu;
|
|
2032
|
+
}
|
|
2033
|
+
/** @deprecated Use {@link ShowOverflowMenu}. */
|
|
2034
|
+
set showOverflowMenu(value) {
|
|
2035
|
+
this.ShowOverflowMenu = value;
|
|
2036
|
+
}
|
|
1918
2037
|
// Export dialog state
|
|
1919
|
-
|
|
1920
|
-
|
|
2038
|
+
ShowExportDialog = false;
|
|
2039
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
2040
|
+
get showExportDialog() {
|
|
2041
|
+
return this.ShowExportDialog;
|
|
2042
|
+
}
|
|
2043
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
2044
|
+
set showExportDialog(value) {
|
|
2045
|
+
this.ShowExportDialog = value;
|
|
2046
|
+
}
|
|
2047
|
+
ExportDialogConfig = null;
|
|
2048
|
+
/** @deprecated Use {@link ExportDialogConfig}. */
|
|
2049
|
+
get exportDialogConfig() {
|
|
2050
|
+
return this.ExportDialogConfig;
|
|
2051
|
+
}
|
|
2052
|
+
/** @deprecated Use {@link ExportDialogConfig}. */
|
|
2053
|
+
set exportDialogConfig(value) {
|
|
2054
|
+
this.ExportDialogConfig = value;
|
|
2055
|
+
}
|
|
1921
2056
|
constructor(cdr, elementRef, exportService, ngZone) {
|
|
1922
2057
|
super();
|
|
1923
2058
|
this.cdr = cdr;
|
|
@@ -2318,7 +2453,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2318
2453
|
this.buildAgColumnDefs();
|
|
2319
2454
|
// Update AG Grid with new column definitions to apply header styles
|
|
2320
2455
|
if (this.gridApi && !this.gridApi.isDestroyed()) {
|
|
2321
|
-
this.gridApi.setGridOption('columnDefs', this.
|
|
2456
|
+
this.gridApi.setGridOption('columnDefs', this.AgColumnDefs);
|
|
2322
2457
|
// Refresh header to apply new header styles
|
|
2323
2458
|
this.gridApi.refreshHeader();
|
|
2324
2459
|
}
|
|
@@ -2351,7 +2486,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2351
2486
|
if (this._gridState.aggregates) {
|
|
2352
2487
|
this._aggregatesConfig = this._gridState.aggregates;
|
|
2353
2488
|
// Fetch aggregate values when gridState aggregates change
|
|
2354
|
-
this.
|
|
2489
|
+
this.RefreshAggregates();
|
|
2355
2490
|
}
|
|
2356
2491
|
// Clear suppression after AG Grid's async events have been processed.
|
|
2357
2492
|
// setTimeout(0) fires after microtasks and rAF, catching all async sort events.
|
|
@@ -2363,8 +2498,8 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2363
2498
|
* Used when gridState changes and includes new aggregate config.
|
|
2364
2499
|
* This runs a RunView with MaxRows=0 to get only aggregate results.
|
|
2365
2500
|
*/
|
|
2366
|
-
async
|
|
2367
|
-
const effectiveAggConfig = this.
|
|
2501
|
+
async RefreshAggregates() {
|
|
2502
|
+
const effectiveAggConfig = this.effectiveAggregatesConfig;
|
|
2368
2503
|
if (!effectiveAggConfig?.expressions?.length) {
|
|
2369
2504
|
this._aggregateResults = [];
|
|
2370
2505
|
this._aggregateValues.clear();
|
|
@@ -2428,13 +2563,17 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2428
2563
|
});
|
|
2429
2564
|
}
|
|
2430
2565
|
}
|
|
2566
|
+
/** @deprecated Use {@link RefreshAggregates}. */
|
|
2567
|
+
async refreshAggregates() {
|
|
2568
|
+
return this.RefreshAggregates();
|
|
2569
|
+
}
|
|
2431
2570
|
onFilterTextChanged() {
|
|
2432
2571
|
// Rebuild column defs to update cell renderers with new filter text
|
|
2433
2572
|
this.buildAgColumnDefs();
|
|
2434
2573
|
// Update AG Grid with new column definitions and refresh cells
|
|
2435
2574
|
if (this.gridApi) {
|
|
2436
2575
|
// Update the column definitions in AG Grid
|
|
2437
|
-
this.gridApi.setGridOption('columnDefs', this.
|
|
2576
|
+
this.gridApi.setGridOption('columnDefs', this.AgColumnDefs);
|
|
2438
2577
|
// Force refresh all cells to apply new highlighting
|
|
2439
2578
|
this.gridApi.refreshCells({ force: true });
|
|
2440
2579
|
// Apply AG Grid quick filter to actually filter rows client-side
|
|
@@ -2668,17 +2807,33 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2668
2807
|
// AG Grid Column Building
|
|
2669
2808
|
// ========================================
|
|
2670
2809
|
buildAgColumnDefs() {
|
|
2671
|
-
|
|
2672
|
-
|
|
2810
|
+
// A grid state whose settings name NONE of this entity's fields is not a column preference —
|
|
2811
|
+
// it is evidence the state belongs to a different entity. `_gridState` outlives an entity
|
|
2812
|
+
// change when one viewer is rebound from entity A to entity B (`EntityName` is a rebindable
|
|
2813
|
+
// input), every setting is then dropped for naming a field B does not have, and taking that
|
|
2814
|
+
// empty array as the answer rendered a grid with no header and no cells while the rows loaded
|
|
2815
|
+
// and the row count read correctly — no error, no warning. So treat an empty result as NO
|
|
2816
|
+
// usable state and fall through to the sources that describe the entity actually in hand.
|
|
2817
|
+
// This is the floor `generateAgColumnDefs()` already has for an entity with no DefaultInView
|
|
2818
|
+
// fields; the grid-state branch was the one path without one.
|
|
2819
|
+
//
|
|
2820
|
+
// Only a TOTAL miss is treated this way. One surviving setting is a real preference — a saved
|
|
2821
|
+
// view whose entity has since lost a field — and discarding it would throw away the user's
|
|
2822
|
+
// columns to guess at state that is legitimately theirs.
|
|
2823
|
+
const fromGridState = (this._gridState?.columnSettings?.length && this._entityInfo)
|
|
2824
|
+
? this.buildAgColumnDefsFromGridState(this._gridState.columnSettings)
|
|
2825
|
+
: [];
|
|
2826
|
+
if (fromGridState.length > 0) {
|
|
2827
|
+
this.AgColumnDefs = fromGridState;
|
|
2673
2828
|
}
|
|
2674
2829
|
else if (this._columns.length > 0) {
|
|
2675
|
-
this.
|
|
2830
|
+
this.AgColumnDefs = this._columns.map(col => this.mapColumnConfigToColDef(col));
|
|
2676
2831
|
}
|
|
2677
2832
|
else if (this._entityInfo) {
|
|
2678
|
-
this.
|
|
2833
|
+
this.AgColumnDefs = this.generateAgColumnDefs(this._entityInfo);
|
|
2679
2834
|
}
|
|
2680
2835
|
else {
|
|
2681
|
-
this.
|
|
2836
|
+
this.AgColumnDefs = [];
|
|
2682
2837
|
}
|
|
2683
2838
|
// Field security, applied to EVERY branch above rather than inside each one. This is the
|
|
2684
2839
|
// only point all three column sources meet: a saved view's `columnSettings` (the usual path
|
|
@@ -2688,11 +2843,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2688
2843
|
// appended — those are synthetic and have no entity field to check.
|
|
2689
2844
|
const deniedReadFields = this.deniedReadFieldsFor(this._entityInfo);
|
|
2690
2845
|
if (deniedReadFields.size > 0) {
|
|
2691
|
-
this.
|
|
2846
|
+
this.AgColumnDefs = this.AgColumnDefs.filter(c => !c.field || !deniedReadFields.has(c.field.trim().toLowerCase()));
|
|
2692
2847
|
}
|
|
2693
2848
|
// Add row number column if enabled
|
|
2694
|
-
if (this._showRowNumbers && this.
|
|
2695
|
-
this.
|
|
2849
|
+
if (this._showRowNumbers && this.AgColumnDefs.length > 0) {
|
|
2850
|
+
this.AgColumnDefs.unshift({
|
|
2696
2851
|
headerName: '#',
|
|
2697
2852
|
field: ROW_NUMBER_FIELD,
|
|
2698
2853
|
width: 60,
|
|
@@ -2703,8 +2858,8 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2703
2858
|
valueGetter: (params) => params.node ? params.node.rowIndex + 1 : ''
|
|
2704
2859
|
});
|
|
2705
2860
|
}
|
|
2706
|
-
if (this._fillWidth && this.
|
|
2707
|
-
this.
|
|
2861
|
+
if (this._fillWidth && this.AgColumnDefs.length > 0) {
|
|
2862
|
+
this.AgColumnDefs.push(this.buildFillerColumnDef());
|
|
2708
2863
|
}
|
|
2709
2864
|
}
|
|
2710
2865
|
/**
|
|
@@ -2773,7 +2928,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2773
2928
|
if (!this._columnsFromHost || !this._entityInfo || this._columns.length === 0) {
|
|
2774
2929
|
return false;
|
|
2775
2930
|
}
|
|
2776
|
-
const next =
|
|
2931
|
+
const next = CanonicalizeColumnFields(this._entityInfo, this._columns);
|
|
2777
2932
|
const changed = next.some((col, i) => col !== this._columns[i]);
|
|
2778
2933
|
if (changed) {
|
|
2779
2934
|
this._columns = next;
|
|
@@ -2994,7 +3149,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2994
3149
|
const fieldType = field.TSType;
|
|
2995
3150
|
const fieldNameLower = field.Name.toLowerCase();
|
|
2996
3151
|
const extendedType = field.ExtendedType?.toLowerCase() || '';
|
|
2997
|
-
const vc = this.
|
|
3152
|
+
const vc = this.EffectiveVisualConfig;
|
|
2998
3153
|
// Determine special field types using ExtendedType metadata first, then field name patterns as fallback
|
|
2999
3154
|
const isCurrency = customFormat?.type === 'currency' ||
|
|
3000
3155
|
fieldNameLower.includes('amount') ||
|
|
@@ -3135,7 +3290,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3135
3290
|
const useCustomFormat = customFormat && customFormat.type && customFormat.type !== 'auto';
|
|
3136
3291
|
if (useCustomFormat) {
|
|
3137
3292
|
// Use custom formatting
|
|
3138
|
-
displayValue = this.formatValueWithCustomFormat(params.value, customFormat);
|
|
3293
|
+
displayValue = this.formatValueWithCustomFormat(params.value, customFormat, field);
|
|
3139
3294
|
// Check if formatCustomBoolean returned HTML (icon or checkbox)
|
|
3140
3295
|
if (customFormat.type === 'boolean' &&
|
|
3141
3296
|
(customFormat.booleanDisplay === 'icon' || customFormat.booleanDisplay === 'checkbox')) {
|
|
@@ -3159,20 +3314,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3159
3314
|
}
|
|
3160
3315
|
// Date formatting
|
|
3161
3316
|
else if (fieldType === 'Date') {
|
|
3162
|
-
|
|
3163
|
-
if (isNaN(date.getTime())) {
|
|
3164
|
-
displayValue = String(params.value);
|
|
3165
|
-
}
|
|
3166
|
-
else if (vc.friendlyDates) {
|
|
3167
|
-
displayValue = date.toLocaleDateString(undefined, {
|
|
3168
|
-
month: 'short',
|
|
3169
|
-
day: 'numeric',
|
|
3170
|
-
year: 'numeric'
|
|
3171
|
-
});
|
|
3172
|
-
}
|
|
3173
|
-
else {
|
|
3174
|
-
displayValue = date.toISOString().split('T')[0];
|
|
3175
|
-
}
|
|
3317
|
+
displayValue = this.formatDefaultDate(params.value, field, !!vc.friendlyDates);
|
|
3176
3318
|
}
|
|
3177
3319
|
// Currency formatting
|
|
3178
3320
|
else if (fieldType === 'number' && isCurrency) {
|
|
@@ -3290,7 +3432,21 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3290
3432
|
/**
|
|
3291
3433
|
* Format a value using custom ColumnFormat settings
|
|
3292
3434
|
*/
|
|
3293
|
-
|
|
3435
|
+
/**
|
|
3436
|
+
* Default rendering of a date-family cell. A `date` column is a calendar day that arrives as UTC
|
|
3437
|
+
* midnight; a local-zone formatter would land on the previous day for every reader west of
|
|
3438
|
+
* Greenwich (MJ#4210). A timestamp names an instant and stays in local time.
|
|
3439
|
+
*/
|
|
3440
|
+
formatDefaultDate(value, field, friendlyDates) {
|
|
3441
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
3442
|
+
if (isNaN(date.getTime()))
|
|
3443
|
+
return String(value);
|
|
3444
|
+
if (!friendlyDates)
|
|
3445
|
+
return date.toISOString().split('T')[0];
|
|
3446
|
+
const options = { month: 'short', day: 'numeric', year: 'numeric' };
|
|
3447
|
+
return IsDateOnlySQLType(field.Type) ? FormatDateOnly(date, options) : date.toLocaleDateString(undefined, options);
|
|
3448
|
+
}
|
|
3449
|
+
formatValueWithCustomFormat(value, format, field) {
|
|
3294
3450
|
if (value == null)
|
|
3295
3451
|
return '—';
|
|
3296
3452
|
switch (format.type) {
|
|
@@ -3302,7 +3458,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3302
3458
|
return this.formatCustomPercent(value, format);
|
|
3303
3459
|
case 'date':
|
|
3304
3460
|
case 'datetime':
|
|
3305
|
-
return this.formatCustomDate(value, format);
|
|
3461
|
+
return this.formatCustomDate(value, format, IsDateOnlySQLType(field.Type));
|
|
3306
3462
|
case 'boolean':
|
|
3307
3463
|
return this.formatCustomBoolean(value, format);
|
|
3308
3464
|
default:
|
|
@@ -3344,7 +3500,12 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3344
3500
|
// Assume value is already a percentage (e.g., 50 = 50%), divide by 100
|
|
3345
3501
|
return new Intl.NumberFormat('en-US', options).format(num / 100);
|
|
3346
3502
|
}
|
|
3347
|
-
|
|
3503
|
+
/**
|
|
3504
|
+
* @param dateOnly The column is a SQL `date`: a calendar day at UTC midnight with no time to show.
|
|
3505
|
+
* It is rendered in UTC so the day does not shift west of Greenwich, and a `datetime` column
|
|
3506
|
+
* format cannot add a time of day to it (MJ#4210). A timestamp column keeps local rendering.
|
|
3507
|
+
*/
|
|
3508
|
+
formatCustomDate(value, format, dateOnly) {
|
|
3348
3509
|
const date = value instanceof Date ? value : new Date(value);
|
|
3349
3510
|
if (isNaN(date.getTime()))
|
|
3350
3511
|
return String(value);
|
|
@@ -3352,6 +3513,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3352
3513
|
const formatStr = format.dateFormat || 'medium';
|
|
3353
3514
|
const includeWeekday = formatStr.includes('-weekday');
|
|
3354
3515
|
const baseFormat = formatStr.replace('-weekday', '');
|
|
3516
|
+
const withTime = format.type === 'datetime' && !dateOnly;
|
|
3355
3517
|
let options;
|
|
3356
3518
|
// Intl.DateTimeFormat doesn't allow combining dateStyle with weekday
|
|
3357
3519
|
// So we must use individual components when weekday is requested
|
|
@@ -3366,7 +3528,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3366
3528
|
// medium
|
|
3367
3529
|
options = { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' };
|
|
3368
3530
|
}
|
|
3369
|
-
if (
|
|
3531
|
+
if (withTime) {
|
|
3370
3532
|
options.hour = 'numeric';
|
|
3371
3533
|
options.minute = '2-digit';
|
|
3372
3534
|
}
|
|
@@ -3376,11 +3538,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3376
3538
|
options = {
|
|
3377
3539
|
dateStyle: baseFormat === 'short' ? 'short' : baseFormat === 'long' ? 'long' : 'medium'
|
|
3378
3540
|
};
|
|
3379
|
-
if (
|
|
3541
|
+
if (withTime) {
|
|
3380
3542
|
options.timeStyle = 'short';
|
|
3381
3543
|
}
|
|
3382
3544
|
}
|
|
3383
|
-
return new Intl.DateTimeFormat('en-US', options).format(date);
|
|
3545
|
+
return dateOnly ? FormatDateOnly(date, options, 'en-US') : new Intl.DateTimeFormat('en-US', options).format(date);
|
|
3384
3546
|
}
|
|
3385
3547
|
formatCustomBoolean(value, format) {
|
|
3386
3548
|
if (format.booleanDisplay === 'icon') {
|
|
@@ -3402,17 +3564,17 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3402
3564
|
updateAgRowSelection() {
|
|
3403
3565
|
switch (this._selectionMode) {
|
|
3404
3566
|
case 'none':
|
|
3405
|
-
this.
|
|
3567
|
+
this.AgRowSelection = { mode: 'singleRow', enableClickSelection: false, checkboxes: false };
|
|
3406
3568
|
break;
|
|
3407
3569
|
case 'single':
|
|
3408
|
-
this.
|
|
3570
|
+
this.AgRowSelection = { mode: 'singleRow', enableClickSelection: true, checkboxes: false };
|
|
3409
3571
|
break;
|
|
3410
3572
|
case 'multiple':
|
|
3411
3573
|
// enableSelectionWithoutKeys replaces deprecated rowMultiSelectWithClick (as of AG Grid v32.2)
|
|
3412
|
-
this.
|
|
3574
|
+
this.AgRowSelection = { mode: 'multiRow', enableClickSelection: true, checkboxes: false, enableSelectionWithoutKeys: true };
|
|
3413
3575
|
break;
|
|
3414
3576
|
case 'checkbox':
|
|
3415
|
-
this.
|
|
3577
|
+
this.AgRowSelection = {
|
|
3416
3578
|
mode: 'multiRow',
|
|
3417
3579
|
enableClickSelection: true,
|
|
3418
3580
|
checkboxes: true,
|
|
@@ -3423,7 +3585,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3423
3585
|
}
|
|
3424
3586
|
// Update the grid if it's already initialized
|
|
3425
3587
|
if (this.gridApi) {
|
|
3426
|
-
this.gridApi.setGridOption('rowSelection', this.
|
|
3588
|
+
this.gridApi.setGridOption('rowSelection', this.AgRowSelection);
|
|
3427
3589
|
}
|
|
3428
3590
|
}
|
|
3429
3591
|
// ========================================
|
|
@@ -3522,7 +3684,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3522
3684
|
runViewParams.UserSearchString = beforeLoadEvent.modifiedParams.searchString;
|
|
3523
3685
|
}
|
|
3524
3686
|
}
|
|
3525
|
-
this.
|
|
3687
|
+
this.Loading = true;
|
|
3526
3688
|
this._aggregatesLoading = true;
|
|
3527
3689
|
this.errorMessage = '';
|
|
3528
3690
|
this.cdr.detectChanges();
|
|
@@ -3530,7 +3692,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3530
3692
|
try {
|
|
3531
3693
|
// Build aggregate expressions from config if present
|
|
3532
3694
|
// Use EffectiveAggregatesConfig to check both _aggregatesConfig and _gridState.aggregates
|
|
3533
|
-
const effectiveAggConfig = this.
|
|
3695
|
+
const effectiveAggConfig = this.effectiveAggregatesConfig;
|
|
3534
3696
|
let aggregateExpressions;
|
|
3535
3697
|
if (effectiveAggConfig?.expressions?.length) {
|
|
3536
3698
|
aggregateExpressions = effectiveAggConfig.expressions
|
|
@@ -3544,7 +3706,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3544
3706
|
const result = await rv.RunView({
|
|
3545
3707
|
...runViewParams,
|
|
3546
3708
|
ResultType: 'simple',
|
|
3547
|
-
Fields: this._entityInfo ?
|
|
3709
|
+
Fields: this._entityInfo ? ComputeFieldsList(this._entityInfo, this._gridState, this.hostColumnFieldNames()) : undefined,
|
|
3548
3710
|
Aggregates: aggregateExpressions
|
|
3549
3711
|
});
|
|
3550
3712
|
const loadTimeMs = performance.now() - startTime;
|
|
@@ -3581,7 +3743,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3581
3743
|
}
|
|
3582
3744
|
finally {
|
|
3583
3745
|
this.ngZone.run(() => {
|
|
3584
|
-
this.
|
|
3746
|
+
this.Loading = false;
|
|
3585
3747
|
this.cdr.detectChanges();
|
|
3586
3748
|
});
|
|
3587
3749
|
}
|
|
@@ -3637,7 +3799,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3637
3799
|
const result = await rv.RunView({
|
|
3638
3800
|
...runViewParams,
|
|
3639
3801
|
ResultType: 'simple',
|
|
3640
|
-
Fields: this._entityInfo ?
|
|
3802
|
+
Fields: this._entityInfo ? ComputeFieldsList(this._entityInfo, this._gridState, this.hostColumnFieldNames()) : undefined
|
|
3641
3803
|
});
|
|
3642
3804
|
if (result.Success) {
|
|
3643
3805
|
const entities = result.Results || [];
|
|
@@ -3730,7 +3892,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3730
3892
|
processData() {
|
|
3731
3893
|
this._rowDataMap.clear();
|
|
3732
3894
|
const dataSource = this._useExternalData ? this._data : this._allData;
|
|
3733
|
-
this.
|
|
3895
|
+
this.RowData = dataSource.map((entity, index) => {
|
|
3734
3896
|
const key = this.getRowKey(entity);
|
|
3735
3897
|
const rowData = {
|
|
3736
3898
|
key,
|
|
@@ -3791,7 +3953,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3791
3953
|
getRowKey(entity) {
|
|
3792
3954
|
// Build key from EntityInfo PK fields when available
|
|
3793
3955
|
if (this._entityInfo) {
|
|
3794
|
-
return
|
|
3956
|
+
return BuildPkString(entity, this._entityInfo);
|
|
3795
3957
|
}
|
|
3796
3958
|
// Fallback to configured key field via direct property access
|
|
3797
3959
|
const keyValue = entity[this._keyField];
|
|
@@ -3807,7 +3969,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3807
3969
|
// ========================================
|
|
3808
3970
|
// AG Grid Event Handlers
|
|
3809
3971
|
// ========================================
|
|
3810
|
-
|
|
3972
|
+
OnGridReady(event) {
|
|
3811
3973
|
this.gridApi = event.api;
|
|
3812
3974
|
// Let the quick filter (search box) match values in HIDDEN columns too (bug C2). AG Grid
|
|
3813
3975
|
// defaults `includeHiddenColumnsInQuickFilter` to false, so without this a search term that
|
|
@@ -3826,6 +3988,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3826
3988
|
this.setupInfiniteScroll();
|
|
3827
3989
|
}
|
|
3828
3990
|
}
|
|
3991
|
+
/** @deprecated Use {@link OnGridReady}. */
|
|
3992
|
+
onGridReady(event) {
|
|
3993
|
+
return this.OnGridReady(event);
|
|
3994
|
+
}
|
|
3829
3995
|
/**
|
|
3830
3996
|
* Smart column auto-sizing that respects our width estimates
|
|
3831
3997
|
* while ensuring the grid fills available space appropriately
|
|
@@ -3835,10 +4001,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3835
4001
|
// The proportional stretch below would fight that: sizeColumnsToFit() assigns explicit widths to
|
|
3836
4002
|
// every column, which clears the filler's flex and re-introduces the stretching of real columns
|
|
3837
4003
|
// that the filler exists to avoid.
|
|
3838
|
-
if (this.
|
|
4004
|
+
if (this.AgColumnDefs.some(col => col.flex))
|
|
3839
4005
|
return;
|
|
3840
4006
|
// Get total estimated width from our column definitions
|
|
3841
|
-
const totalEstimatedWidth = this.
|
|
4007
|
+
const totalEstimatedWidth = this.AgColumnDefs.reduce((sum, col) => sum + (col.width || 150), 0);
|
|
3842
4008
|
// Get available width from the grid container
|
|
3843
4009
|
const gridElement = this.elementRef.nativeElement.querySelector('.mj-ag-grid');
|
|
3844
4010
|
const availableWidth = gridElement?.clientWidth || 0;
|
|
@@ -3859,7 +4025,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3859
4025
|
// If our columns are wider than available space, let the user scroll horizontally
|
|
3860
4026
|
// (AG Grid handles this automatically)
|
|
3861
4027
|
}
|
|
3862
|
-
|
|
4028
|
+
OnAgRowClicked(event) {
|
|
3863
4029
|
if (!this._entityInfo || !event.data)
|
|
3864
4030
|
return;
|
|
3865
4031
|
const pkString = event.data['__pk'];
|
|
@@ -3880,7 +4046,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3880
4046
|
this.emitNavigationRequest(rowData.entity, pkString);
|
|
3881
4047
|
}
|
|
3882
4048
|
}
|
|
3883
|
-
|
|
4049
|
+
/** @deprecated Use {@link OnAgRowClicked}. */
|
|
4050
|
+
onAgRowClicked(event) {
|
|
4051
|
+
return this.OnAgRowClicked(event);
|
|
4052
|
+
}
|
|
4053
|
+
OnAgRowDoubleClicked(event) {
|
|
3884
4054
|
if (!this._entityInfo || !event.data)
|
|
3885
4055
|
return;
|
|
3886
4056
|
const pkString = event.data['__pk'];
|
|
@@ -3901,12 +4071,16 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3901
4071
|
this.emitNavigationRequest(rowData.entity, pkString);
|
|
3902
4072
|
}
|
|
3903
4073
|
}
|
|
4074
|
+
/** @deprecated Use {@link OnAgRowDoubleClicked}. */
|
|
4075
|
+
onAgRowDoubleClicked(event) {
|
|
4076
|
+
return this.OnAgRowDoubleClicked(event);
|
|
4077
|
+
}
|
|
3904
4078
|
/**
|
|
3905
4079
|
* Handles cell click events to detect FK link clicks.
|
|
3906
4080
|
* When a user clicks on a foreign key link, emits ForeignKeyClick event
|
|
3907
4081
|
* for the parent component to handle navigation.
|
|
3908
4082
|
*/
|
|
3909
|
-
|
|
4083
|
+
OnAgCellClicked(event) {
|
|
3910
4084
|
// Check if the click was on an FK link
|
|
3911
4085
|
const target = event.event?.target;
|
|
3912
4086
|
if (!target) {
|
|
@@ -3936,6 +4110,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3936
4110
|
//console.log('[FK Debug] Missing required data attributes, not emitting');
|
|
3937
4111
|
}
|
|
3938
4112
|
}
|
|
4113
|
+
/** @deprecated Use {@link OnAgCellClicked}. */
|
|
4114
|
+
onAgCellClicked(event) {
|
|
4115
|
+
return this.OnAgCellClicked(event);
|
|
4116
|
+
}
|
|
3939
4117
|
/**
|
|
3940
4118
|
* Emits a navigation request for the given entity record.
|
|
3941
4119
|
*/
|
|
@@ -3948,7 +4126,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3948
4126
|
compositeKey
|
|
3949
4127
|
});
|
|
3950
4128
|
}
|
|
3951
|
-
|
|
4129
|
+
OnAgSortChanged(event) {
|
|
3952
4130
|
if (this.suppressSortEvents) {
|
|
3953
4131
|
return;
|
|
3954
4132
|
}
|
|
@@ -3996,7 +4174,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3996
4174
|
this.emitGridStateChanged('sort');
|
|
3997
4175
|
}
|
|
3998
4176
|
}
|
|
3999
|
-
|
|
4177
|
+
/** @deprecated Use {@link OnAgSortChanged}. */
|
|
4178
|
+
onAgSortChanged(event) {
|
|
4179
|
+
return this.OnAgSortChanged(event);
|
|
4180
|
+
}
|
|
4181
|
+
OnAgSelectionChanged(event) {
|
|
4000
4182
|
const selectedNodes = event.api.getSelectedNodes();
|
|
4001
4183
|
const previousSelection = [...this._selectedKeys];
|
|
4002
4184
|
const newSelection = selectedNodes.map(node => node.data['__pk']);
|
|
@@ -4040,20 +4222,32 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4040
4222
|
}
|
|
4041
4223
|
this.SelectionChange.emit(this._selectedKeys);
|
|
4042
4224
|
}
|
|
4043
|
-
|
|
4225
|
+
/** @deprecated Use {@link OnAgSelectionChanged}. */
|
|
4226
|
+
onAgSelectionChanged(event) {
|
|
4227
|
+
return this.OnAgSelectionChanged(event);
|
|
4228
|
+
}
|
|
4229
|
+
OnAgColumnResized(event) {
|
|
4044
4230
|
if (event.finished && event.source !== 'api') {
|
|
4045
4231
|
// User manually resized a column - mark as dirty
|
|
4046
4232
|
this._isGridStateDirty = true;
|
|
4047
4233
|
this.emitGridStateChanged('columns');
|
|
4048
4234
|
}
|
|
4049
4235
|
}
|
|
4050
|
-
|
|
4236
|
+
/** @deprecated Use {@link OnAgColumnResized}. */
|
|
4237
|
+
onAgColumnResized(event) {
|
|
4238
|
+
return this.OnAgColumnResized(event);
|
|
4239
|
+
}
|
|
4240
|
+
OnAgColumnMoved(event) {
|
|
4051
4241
|
if (event.finished && event.source !== 'api') {
|
|
4052
4242
|
// User manually moved a column - mark as dirty
|
|
4053
4243
|
this._isGridStateDirty = true;
|
|
4054
4244
|
this.emitGridStateChanged('columns');
|
|
4055
4245
|
}
|
|
4056
4246
|
}
|
|
4247
|
+
/** @deprecated Use {@link OnAgColumnMoved}. */
|
|
4248
|
+
onAgColumnMoved(event) {
|
|
4249
|
+
return this.OnAgColumnMoved(event);
|
|
4250
|
+
}
|
|
4057
4251
|
// ========================================
|
|
4058
4252
|
// Grid State Management
|
|
4059
4253
|
// ========================================
|
|
@@ -4073,7 +4267,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4073
4267
|
});
|
|
4074
4268
|
// Schedule auto-persist if enabled, but NOT during view transitions
|
|
4075
4269
|
// (suppressPersist prevents old view's state from being saved to new view)
|
|
4076
|
-
if (this._autoPersistState && !this._suppressPersist && this.
|
|
4270
|
+
if (this._autoPersistState && !this._suppressPersist && this.CanEditCurrentView) {
|
|
4077
4271
|
if (!this.IsDynamicView && this._viewEntity) {
|
|
4078
4272
|
// Stored view - persist to UserView.GridState (debounced)
|
|
4079
4273
|
// Track pending state for flush on destroy
|
|
@@ -4385,7 +4579,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4385
4579
|
this._data = [];
|
|
4386
4580
|
this._rowDataMap.clear();
|
|
4387
4581
|
this._selectedKeys = [];
|
|
4388
|
-
this.
|
|
4582
|
+
this.RowData = [];
|
|
4389
4583
|
this.totalRowCount = 0;
|
|
4390
4584
|
this.cdr.detectChanges();
|
|
4391
4585
|
}
|
|
@@ -4457,7 +4651,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4457
4651
|
// ========================================
|
|
4458
4652
|
// Toolbar Click Handlers
|
|
4459
4653
|
// ========================================
|
|
4460
|
-
|
|
4654
|
+
OnAddClick() {
|
|
4461
4655
|
// Emit legacy events for backward compatibility
|
|
4462
4656
|
this.AddRequested.emit();
|
|
4463
4657
|
this.NewButtonClick.emit();
|
|
@@ -4477,19 +4671,31 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4477
4671
|
}
|
|
4478
4672
|
}
|
|
4479
4673
|
}
|
|
4480
|
-
|
|
4674
|
+
/** @deprecated Use {@link OnAddClick}. */
|
|
4675
|
+
onAddClick() {
|
|
4676
|
+
return this.OnAddClick();
|
|
4677
|
+
}
|
|
4678
|
+
OnDeleteClick() {
|
|
4481
4679
|
const selectedRows = this.GetSelectedRows();
|
|
4482
4680
|
if (selectedRows.length > 0) {
|
|
4483
4681
|
this.DeleteRequested.emit(selectedRows);
|
|
4484
4682
|
this.DeleteButtonClick.emit(selectedRows);
|
|
4485
4683
|
}
|
|
4486
4684
|
}
|
|
4487
|
-
|
|
4685
|
+
/** @deprecated Use {@link OnDeleteClick}. */
|
|
4686
|
+
onDeleteClick() {
|
|
4687
|
+
return this.OnDeleteClick();
|
|
4688
|
+
}
|
|
4689
|
+
OnExportClick() {
|
|
4488
4690
|
this.ExportRequested.emit();
|
|
4489
4691
|
this.ExportButtonClick.emit();
|
|
4490
4692
|
// Show the export dialog
|
|
4491
4693
|
void this.showExportDialogForCurrentData();
|
|
4492
4694
|
}
|
|
4695
|
+
/** @deprecated Use {@link OnExportClick}. */
|
|
4696
|
+
onExportClick() {
|
|
4697
|
+
return this.OnExportClick();
|
|
4698
|
+
}
|
|
4493
4699
|
/**
|
|
4494
4700
|
* Shows the export dialog for the full result set (falling back to the loaded page when no
|
|
4495
4701
|
* ExportDataProvider is supplied).
|
|
@@ -4506,7 +4712,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4506
4712
|
}
|
|
4507
4713
|
const columns = this.GetExportColumns();
|
|
4508
4714
|
const fileName = this.getDefaultExportFileName();
|
|
4509
|
-
this.
|
|
4715
|
+
this.ExportDialogConfig = {
|
|
4510
4716
|
data,
|
|
4511
4717
|
columns,
|
|
4512
4718
|
defaultFileName: fileName,
|
|
@@ -4516,17 +4722,21 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4516
4722
|
defaultSamplingMode: 'all',
|
|
4517
4723
|
dialogTitle: `Export ${this._entityInfo?.Name || 'Data'}`
|
|
4518
4724
|
};
|
|
4519
|
-
this.
|
|
4725
|
+
this.ShowExportDialog = true;
|
|
4520
4726
|
this.cdr.detectChanges();
|
|
4521
4727
|
}
|
|
4522
4728
|
/**
|
|
4523
4729
|
* Handle export dialog close
|
|
4524
4730
|
*/
|
|
4525
|
-
|
|
4526
|
-
this.
|
|
4527
|
-
this.
|
|
4731
|
+
OnExportDialogClosed(result) {
|
|
4732
|
+
this.ShowExportDialog = false;
|
|
4733
|
+
this.ExportDialogConfig = null;
|
|
4528
4734
|
this.cdr.detectChanges();
|
|
4529
4735
|
}
|
|
4736
|
+
/** @deprecated Use {@link OnExportDialogClosed}. */
|
|
4737
|
+
onExportDialogClosed(result) {
|
|
4738
|
+
return this.OnExportDialogClosed(result);
|
|
4739
|
+
}
|
|
4530
4740
|
/**
|
|
4531
4741
|
* Export grid data directly without showing dialog.
|
|
4532
4742
|
* Use this for programmatic export with specific options.
|
|
@@ -4596,7 +4806,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4596
4806
|
}
|
|
4597
4807
|
}
|
|
4598
4808
|
// rowData is already plain Record<string, unknown>[] objects - return directly
|
|
4599
|
-
return this.
|
|
4809
|
+
return this.RowData.map(row => row);
|
|
4600
4810
|
}
|
|
4601
4811
|
/**
|
|
4602
4812
|
* The columns the grid is showing, in the order it shows them, with the headers it shows. This is the
|
|
@@ -4610,7 +4820,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4610
4820
|
const displayed = this.gridApi?.getAllDisplayedColumns() ?? [];
|
|
4611
4821
|
const defs = displayed.length > 0
|
|
4612
4822
|
? displayed.map(c => c.getColDef())
|
|
4613
|
-
: this.
|
|
4823
|
+
: this.AgColumnDefs.filter(d => !d.hide);
|
|
4614
4824
|
// No `width`: ExportColumn.width is in characters, while grid widths are pixels, so passing them
|
|
4615
4825
|
// made a 150px column 150 characters wide. Left unset, the Excel exporter auto-fits each column.
|
|
4616
4826
|
return defs
|
|
@@ -4662,11 +4872,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4662
4872
|
const timestamp = new Date().toISOString().slice(0, 10);
|
|
4663
4873
|
return `${entityName}_${timestamp}`;
|
|
4664
4874
|
}
|
|
4665
|
-
|
|
4875
|
+
OnRefreshClick() {
|
|
4666
4876
|
this.RefreshButtonClick.emit();
|
|
4667
4877
|
this.Refresh();
|
|
4668
4878
|
}
|
|
4669
|
-
|
|
4879
|
+
/** @deprecated Use {@link OnRefreshClick}. */
|
|
4880
|
+
onRefreshClick() {
|
|
4881
|
+
return this.OnRefreshClick();
|
|
4882
|
+
}
|
|
4883
|
+
OnCompareClick() {
|
|
4670
4884
|
const selectedRows = this.GetSelectedRows();
|
|
4671
4885
|
if (selectedRows.length >= 2) {
|
|
4672
4886
|
// Emit legacy event for backward compatibility
|
|
@@ -4680,7 +4894,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4680
4894
|
}
|
|
4681
4895
|
}
|
|
4682
4896
|
}
|
|
4683
|
-
|
|
4897
|
+
/** @deprecated Use {@link OnCompareClick}. */
|
|
4898
|
+
onCompareClick() {
|
|
4899
|
+
return this.OnCompareClick();
|
|
4900
|
+
}
|
|
4901
|
+
OnMergeClick() {
|
|
4684
4902
|
const selectedRows = this.GetSelectedRows();
|
|
4685
4903
|
if (selectedRows.length >= 2) {
|
|
4686
4904
|
// Emit legacy event for backward compatibility
|
|
@@ -4694,7 +4912,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4694
4912
|
}
|
|
4695
4913
|
}
|
|
4696
4914
|
}
|
|
4697
|
-
|
|
4915
|
+
/** @deprecated Use {@link OnMergeClick}. */
|
|
4916
|
+
onMergeClick() {
|
|
4917
|
+
return this.OnMergeClick();
|
|
4918
|
+
}
|
|
4919
|
+
async OnAddToListClick() {
|
|
4698
4920
|
let rows = this.GetSelectedRows();
|
|
4699
4921
|
// No explicit selection → add EVERY matching record, not just the loaded page. Without this,
|
|
4700
4922
|
// building a list from a sizeable view meant selecting-all page by page (bug E3). We fetch the
|
|
@@ -4720,7 +4942,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4720
4942
|
// Emit new structured event with record IDs for list management
|
|
4721
4943
|
if (this._entityInfo) {
|
|
4722
4944
|
const recordIds = rows.map(r => {
|
|
4723
|
-
return
|
|
4945
|
+
return BuildPkString(r, this._entityInfo);
|
|
4724
4946
|
});
|
|
4725
4947
|
this.AddToListRequested.emit({
|
|
4726
4948
|
entityInfo: this._entityInfo,
|
|
@@ -4730,7 +4952,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4730
4952
|
}
|
|
4731
4953
|
}
|
|
4732
4954
|
}
|
|
4733
|
-
|
|
4955
|
+
/** @deprecated Use {@link OnAddToListClick}. */
|
|
4956
|
+
async onAddToListClick() {
|
|
4957
|
+
return this.OnAddToListClick();
|
|
4958
|
+
}
|
|
4959
|
+
OnDuplicateSearchClick() {
|
|
4734
4960
|
const selectedRows = this.GetSelectedRows();
|
|
4735
4961
|
if (selectedRows.length >= 2) {
|
|
4736
4962
|
// Emit legacy event for backward compatibility
|
|
@@ -4744,7 +4970,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4744
4970
|
}
|
|
4745
4971
|
}
|
|
4746
4972
|
}
|
|
4747
|
-
|
|
4973
|
+
/** @deprecated Use {@link OnDuplicateSearchClick}. */
|
|
4974
|
+
onDuplicateSearchClick() {
|
|
4975
|
+
return this.OnDuplicateSearchClick();
|
|
4976
|
+
}
|
|
4977
|
+
OnCommunicationClick() {
|
|
4748
4978
|
const selectedRows = this.GetSelectedRows();
|
|
4749
4979
|
if (selectedRows.length > 0) {
|
|
4750
4980
|
// Emit legacy event for backward compatibility
|
|
@@ -4759,6 +4989,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4759
4989
|
}
|
|
4760
4990
|
}
|
|
4761
4991
|
}
|
|
4992
|
+
/** @deprecated Use {@link OnCommunicationClick}. */
|
|
4993
|
+
onCommunicationClick() {
|
|
4994
|
+
return this.OnCommunicationClick();
|
|
4995
|
+
}
|
|
4762
4996
|
/**
|
|
4763
4997
|
* "Manage Columns" / column-chooser toolbar affordance. The grid is generic and doesn't own a
|
|
4764
4998
|
* column-management UI, so it raises {@link ManageColumnsRequested} for its host to handle —
|
|
@@ -4766,9 +5000,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4766
5000
|
* column editor backed by `UserView.GridState`. Hosts that embed the grid standalone can handle
|
|
4767
5001
|
* this to surface their own column UI.
|
|
4768
5002
|
*/
|
|
4769
|
-
|
|
5003
|
+
OnColumnChooserClick() {
|
|
4770
5004
|
this.ManageColumnsRequested.emit();
|
|
4771
5005
|
}
|
|
5006
|
+
/** @deprecated Use {@link OnColumnChooserClick}. */
|
|
5007
|
+
onColumnChooserClick() {
|
|
5008
|
+
return this.OnColumnChooserClick();
|
|
5009
|
+
}
|
|
4772
5010
|
/**
|
|
4773
5011
|
* The runtime-UX driver currently mounted over the grid (e.g. the Record Process bulk-update runner),
|
|
4774
5012
|
* or null when none. Set when an action whose invocation names a `RuntimeUXDriverClass` is clicked.
|
|
@@ -4779,7 +5017,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4779
5017
|
* grid mounts that interactive driver in-place (no parent wiring required); otherwise it emits
|
|
4780
5018
|
* `EntityActionRequested` for the host to invoke the action the classic way.
|
|
4781
5019
|
*/
|
|
4782
|
-
|
|
5020
|
+
OnEntityActionClick(action) {
|
|
4783
5021
|
if (!this._entityInfo)
|
|
4784
5022
|
return;
|
|
4785
5023
|
if (action.runtimeUXDriverClass) {
|
|
@@ -4792,6 +5030,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4792
5030
|
selectedRecords: this.GetSelectedRows()
|
|
4793
5031
|
});
|
|
4794
5032
|
}
|
|
5033
|
+
/** @deprecated Use {@link OnEntityActionClick}. */
|
|
5034
|
+
onEntityActionClick(action) {
|
|
5035
|
+
return this.OnEntityActionClick(action);
|
|
5036
|
+
}
|
|
4795
5037
|
/** Builds the driver context from the current entity + selection and mounts the named driver. */
|
|
4796
5038
|
mountRuntimeDriver(action) {
|
|
4797
5039
|
const entity = this._entityInfo;
|
|
@@ -4828,7 +5070,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4828
5070
|
/**
|
|
4829
5071
|
* Checks if an entity action is currently enabled based on selection requirements
|
|
4830
5072
|
*/
|
|
4831
|
-
|
|
5073
|
+
IsEntityActionEnabled(action) {
|
|
4832
5074
|
if (!action.requiresSelection)
|
|
4833
5075
|
return true;
|
|
4834
5076
|
const selectedCount = this._selectedKeys.length;
|
|
@@ -4840,14 +5082,18 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4840
5082
|
return false;
|
|
4841
5083
|
return true;
|
|
4842
5084
|
}
|
|
5085
|
+
/** @deprecated Use {@link IsEntityActionEnabled}. */
|
|
5086
|
+
isEntityActionEnabled(action) {
|
|
5087
|
+
return this.IsEntityActionEnabled(action);
|
|
5088
|
+
}
|
|
4843
5089
|
// ========================================
|
|
4844
5090
|
// Overflow Menu Methods
|
|
4845
5091
|
// ========================================
|
|
4846
|
-
|
|
4847
|
-
this.
|
|
5092
|
+
ToggleOverflowMenu() {
|
|
5093
|
+
this.ShowOverflowMenu = !this.ShowOverflowMenu;
|
|
4848
5094
|
this.cdr.detectChanges();
|
|
4849
5095
|
// Add click outside listener when menu is open
|
|
4850
|
-
if (this.
|
|
5096
|
+
if (this.ShowOverflowMenu) {
|
|
4851
5097
|
setTimeout(() => {
|
|
4852
5098
|
document.addEventListener('click', this.handleOutsideClick);
|
|
4853
5099
|
}, 0);
|
|
@@ -4856,35 +5102,63 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4856
5102
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
4857
5103
|
}
|
|
4858
5104
|
}
|
|
4859
|
-
|
|
4860
|
-
|
|
5105
|
+
/** @deprecated Use {@link ToggleOverflowMenu}. */
|
|
5106
|
+
toggleOverflowMenu() {
|
|
5107
|
+
return this.ToggleOverflowMenu();
|
|
5108
|
+
}
|
|
5109
|
+
CloseOverflowMenu() {
|
|
5110
|
+
this.ShowOverflowMenu = false;
|
|
4861
5111
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
4862
5112
|
this.cdr.detectChanges();
|
|
4863
5113
|
}
|
|
5114
|
+
/** @deprecated Use {@link CloseOverflowMenu}. */
|
|
5115
|
+
closeOverflowMenu() {
|
|
5116
|
+
return this.CloseOverflowMenu();
|
|
5117
|
+
}
|
|
4864
5118
|
handleOutsideClick = () => {
|
|
4865
|
-
this.
|
|
5119
|
+
this.CloseOverflowMenu();
|
|
4866
5120
|
};
|
|
4867
5121
|
// Overflow menu visibility helpers
|
|
4868
|
-
get
|
|
4869
|
-
return this.
|
|
4870
|
-
this.
|
|
5122
|
+
get HasOverflowMenuItems() {
|
|
5123
|
+
return this.ShowExportInOverflow ||
|
|
5124
|
+
this.ShowColumnChooserInOverflow ||
|
|
4871
5125
|
(this._showEntityActionButtons && this._entityActions.length > 0) ||
|
|
4872
|
-
this.
|
|
5126
|
+
this.HasSelectionDependentOverflowActions;
|
|
4873
5127
|
}
|
|
4874
|
-
|
|
5128
|
+
/** @deprecated Use {@link HasOverflowMenuItems}. */
|
|
5129
|
+
get hasOverflowMenuItems() {
|
|
5130
|
+
return this.HasOverflowMenuItems;
|
|
5131
|
+
}
|
|
5132
|
+
get ShowExportInOverflow() {
|
|
4875
5133
|
// Export is in overflow when it's not shown as a primary button
|
|
4876
5134
|
return !this.ShowExportButton && !!this.ToolbarConfig.showExport;
|
|
4877
5135
|
}
|
|
4878
|
-
|
|
5136
|
+
/** @deprecated Use {@link ShowExportInOverflow}. */
|
|
5137
|
+
get showExportInOverflow() {
|
|
5138
|
+
return this.ShowExportInOverflow;
|
|
5139
|
+
}
|
|
5140
|
+
get ShowColumnChooserInOverflow() {
|
|
4879
5141
|
return this.AllowColumnToggle && !this.ToolbarConfig.showColumnChooser;
|
|
4880
5142
|
}
|
|
5143
|
+
/** @deprecated Use {@link ShowColumnChooserInOverflow}. */
|
|
5144
|
+
get showColumnChooserInOverflow() {
|
|
5145
|
+
return this.ShowColumnChooserInOverflow;
|
|
5146
|
+
}
|
|
5147
|
+
get HasSelectionDependentOverflowActions() {
|
|
5148
|
+
return this.ShowCommunicationInOverflow;
|
|
5149
|
+
}
|
|
5150
|
+
/** @deprecated Use {@link HasSelectionDependentOverflowActions}. */
|
|
4881
5151
|
get hasSelectionDependentOverflowActions() {
|
|
4882
|
-
return this.
|
|
5152
|
+
return this.HasSelectionDependentOverflowActions;
|
|
4883
5153
|
}
|
|
4884
|
-
get
|
|
5154
|
+
get ShowCommunicationInOverflow() {
|
|
4885
5155
|
// Communication is in overflow when it's not shown as a primary button
|
|
4886
5156
|
return !this.ShowCommunicationButton && this.HasSelection && this.EntitySupportsCommunication;
|
|
4887
5157
|
}
|
|
5158
|
+
/** @deprecated Use {@link ShowCommunicationInOverflow}. */
|
|
5159
|
+
get showCommunicationInOverflow() {
|
|
5160
|
+
return this.ShowCommunicationInOverflow;
|
|
5161
|
+
}
|
|
4888
5162
|
// ========================================
|
|
4889
5163
|
// Toolbar Button State Helpers
|
|
4890
5164
|
// ========================================
|
|
@@ -4897,13 +5171,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4897
5171
|
// ========================================
|
|
4898
5172
|
// CSS Helpers
|
|
4899
5173
|
// ========================================
|
|
4900
|
-
get
|
|
5174
|
+
get GridContainerClasses() {
|
|
4901
5175
|
const classes = ['mj-grid-container'];
|
|
4902
5176
|
classes.push(`grid-lines-${this._gridLines}`);
|
|
4903
5177
|
if (this._striped)
|
|
4904
5178
|
classes.push('grid-striped');
|
|
4905
5179
|
// Add visual config classes
|
|
4906
|
-
const vc = this.
|
|
5180
|
+
const vc = this.EffectiveVisualConfig;
|
|
4907
5181
|
classes.push(`header-style-${vc.headerStyle}`);
|
|
4908
5182
|
if (vc.headerShadow)
|
|
4909
5183
|
classes.push('header-shadow');
|
|
@@ -4916,11 +5190,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4916
5190
|
classes.push(`checkbox-style-${vc.checkboxStyle}`);
|
|
4917
5191
|
return classes;
|
|
4918
5192
|
}
|
|
5193
|
+
/** @deprecated Use {@link GridContainerClasses}. */
|
|
5194
|
+
get gridContainerClasses() {
|
|
5195
|
+
return this.GridContainerClasses;
|
|
5196
|
+
}
|
|
4919
5197
|
/**
|
|
4920
5198
|
* Apply visual configuration by setting CSS custom properties
|
|
4921
5199
|
*/
|
|
4922
5200
|
applyVisualConfig() {
|
|
4923
|
-
const vc = this.
|
|
5201
|
+
const vc = this.EffectiveVisualConfig;
|
|
4924
5202
|
const el = this.elementRef.nativeElement;
|
|
4925
5203
|
// Set CSS custom properties for dynamic values
|
|
4926
5204
|
if (vc.headerBackground) {
|
|
@@ -4956,16 +5234,20 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4956
5234
|
this.buildAgColumnDefs();
|
|
4957
5235
|
}
|
|
4958
5236
|
}
|
|
4959
|
-
get
|
|
5237
|
+
get GridHeightStyle() {
|
|
4960
5238
|
if (typeof this._height === 'number') {
|
|
4961
5239
|
return `${this._height}px`;
|
|
4962
5240
|
}
|
|
4963
5241
|
return this._height === 'auto' ? '100%' : 'fit-content';
|
|
4964
5242
|
}
|
|
5243
|
+
/** @deprecated Use {@link GridHeightStyle}. */
|
|
5244
|
+
get gridHeightStyle() {
|
|
5245
|
+
return this.GridHeightStyle;
|
|
5246
|
+
}
|
|
4965
5247
|
// ========================================
|
|
4966
5248
|
// Toolbar Button Helpers
|
|
4967
5249
|
// ========================================
|
|
4968
|
-
|
|
5250
|
+
IsButtonVisible(button) {
|
|
4969
5251
|
if (button.visible === undefined)
|
|
4970
5252
|
return true;
|
|
4971
5253
|
if (typeof button.visible === 'boolean')
|
|
@@ -4974,7 +5256,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4974
5256
|
return button.visible();
|
|
4975
5257
|
return true;
|
|
4976
5258
|
}
|
|
4977
|
-
|
|
5259
|
+
/** @deprecated Use {@link IsButtonVisible}. */
|
|
5260
|
+
isButtonVisible(button) {
|
|
5261
|
+
return this.IsButtonVisible(button);
|
|
5262
|
+
}
|
|
5263
|
+
IsButtonDisabled(button) {
|
|
4978
5264
|
if (button.disabled === undefined)
|
|
4979
5265
|
return false;
|
|
4980
5266
|
if (typeof button.disabled === 'boolean')
|
|
@@ -4983,11 +5269,19 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4983
5269
|
return button.disabled();
|
|
4984
5270
|
return false;
|
|
4985
5271
|
}
|
|
4986
|
-
|
|
5272
|
+
/** @deprecated Use {@link IsButtonDisabled}. */
|
|
5273
|
+
isButtonDisabled(button) {
|
|
5274
|
+
return this.IsButtonDisabled(button);
|
|
5275
|
+
}
|
|
5276
|
+
OnToolbarButtonClick(button) {
|
|
4987
5277
|
if (button.onClick) {
|
|
4988
5278
|
button.onClick();
|
|
4989
5279
|
}
|
|
4990
5280
|
}
|
|
5281
|
+
/** @deprecated Use {@link OnToolbarButtonClick}. */
|
|
5282
|
+
onToolbarButtonClick(button) {
|
|
5283
|
+
return this.OnToolbarButtonClick(button);
|
|
5284
|
+
}
|
|
4991
5285
|
// ========================================
|
|
4992
5286
|
// Public Getters
|
|
4993
5287
|
// ========================================
|
|
@@ -5004,15 +5298,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
5004
5298
|
i0.ɵɵviewQuery(_c0, 5);
|
|
5005
5299
|
} if (rf & 2) {
|
|
5006
5300
|
let _t;
|
|
5007
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.
|
|
5008
|
-
} }, inputs: { Params: "Params", AllowLoad: "AllowLoad", AutoRefreshOnParamsChange: "AutoRefreshOnParamsChange", PaginationMode: "PaginationMode", PageSize: "PageSize", CacheBlockSize: "CacheBlockSize", MaxBlocksInCache: "MaxBlocksInCache", ShowPager: "ShowPager", ExportDataProvider: "ExportDataProvider", ShowRecycleBin: "ShowRecycleBin", PagerPageNumber: "PagerPageNumber", TotalRowCount: "TotalRowCount", Data: "Data", Columns: "Columns", GridState: "GridState", AllowColumnReorder: "AllowColumnReorder", AllowColumnResize: "AllowColumnResize", AllowColumnToggle: "AllowColumnToggle", ShowHeader: "ShowHeader", AllowSorting: "AllowSorting", AllowMultiSort: "AllowMultiSort", ServerSideSorting: "ServerSideSorting", AllowColumnFilters: "AllowColumnFilters", ShowSearch: "ShowSearch", SelectionMode: "SelectionMode", SelectedKeys: "SelectedKeys", KeyField: "KeyField", EditMode: "EditMode", AllowAdd: "AllowAdd", AllowDelete: "AllowDelete", Height: "Height", RowHeight: "RowHeight", WrapText: "WrapText", VirtualScroll: "VirtualScroll", ShowRowNumbers: "ShowRowNumbers", FillWidth: "FillWidth", Striped: "Striped", GridLines: "GridLines", VisualConfig: "VisualConfig", ShowToolbar: "ShowToolbar", ToolbarConfig: "ToolbarConfig", StateKey: "StateKey", AutoPersistState: "AutoPersistState", StatePersistDebounce: "StatePersistDebounce", RefreshDebounce: "RefreshDebounce", FilterText: "FilterText", ShowNewButton: "ShowNewButton", ShowRefreshButton: "ShowRefreshButton", ShowExportButton: "ShowExportButton", ShowDeleteButton: "ShowDeleteButton", ShowCompareButton: "ShowCompareButton", ShowMergeButton: "ShowMergeButton", ShowAddToListButton: "ShowAddToListButton", ShowDuplicateSearchButton: "ShowDuplicateSearchButton", ShowCommunicationButton: "ShowCommunicationButton", AutoNavigate: "AutoNavigate", NavigateOnDoubleClick: "NavigateOnDoubleClick", CreateRecordMode: "CreateRecordMode", NewRecordValues: "NewRecordValues", ShowEntityActionButtons: "ShowEntityActionButtons", EntityActions: "EntityActions", AutoLoadEntityActions: "AutoLoadEntityActions", AggregatesConfig: "AggregatesConfig" }, outputs: { PageChange: "PageChange", AggregatesLoaded: "AggregatesLoaded", BeforeRowSelect: "BeforeRowSelect", AfterRowSelect: "AfterRowSelect", BeforeRowDeselect: "BeforeRowDeselect", AfterRowDeselect: "AfterRowDeselect", SelectionChange: "SelectionChange", BeforeRowClick: "BeforeRowClick", AfterRowClick: "AfterRowClick", BeforeRowDoubleClick: "BeforeRowDoubleClick", AfterRowDoubleClick: "AfterRowDoubleClick", ForeignKeyClick: "ForeignKeyClick", BeforeCellEdit: "BeforeCellEdit", AfterCellEditBegin: "AfterCellEditBegin", BeforeCellEditCommit: "BeforeCellEditCommit", AfterCellEditCommit: "AfterCellEditCommit", BeforeCellEditCancel: "BeforeCellEditCancel", AfterCellEditCancel: "AfterCellEditCancel", BeforeRowSave: "BeforeRowSave", AfterRowSave: "AfterRowSave", BeforeRowDelete: "BeforeRowDelete", AfterRowDelete: "AfterRowDelete", BeforeDataLoad: "BeforeDataLoad", AfterDataLoad: "AfterDataLoad", BeforeDataRefresh: "BeforeDataRefresh", AfterDataRefresh: "AfterDataRefresh", BeforeSort: "BeforeSort", AfterSort: "AfterSort", BeforeColumnReorder: "BeforeColumnReorder", AfterColumnReorder: "AfterColumnReorder", BeforeColumnResize: "BeforeColumnResize", AfterColumnResize: "AfterColumnResize", BeforeColumnVisibilityChange: "BeforeColumnVisibilityChange", AfterColumnVisibilityChange: "AfterColumnVisibilityChange", GridStateChanged: "GridStateChanged", AddRequested: "AddRequested", DeleteRequested: "DeleteRequested", ExportRequested: "ExportRequested", NewButtonClick: "NewButtonClick", RefreshButtonClick: "RefreshButtonClick", ExportButtonClick: "ExportButtonClick", DeleteButtonClick: "DeleteButtonClick", CompareButtonClick: "CompareButtonClick", MergeButtonClick: "MergeButtonClick", AddToListButtonClick: "AddToListButtonClick", DuplicateSearchButtonClick: "DuplicateSearchButtonClick", CommunicationButtonClick: "CommunicationButtonClick", ManageColumnsRequested: "ManageColumnsRequested", NavigationRequested: "NavigationRequested", NewRecordDialogRequested: "NewRecordDialogRequested", NewRecordTabRequested: "NewRecordTabRequested", CompareRecordsRequested: "CompareRecordsRequested", MergeRecordsRequested: "MergeRecordsRequested", CommunicationRequested: "CommunicationRequested", DuplicateSearchRequested: "DuplicateSearchRequested", AddToListRequested: "AddToListRequested", LoadEntityActionsRequested: "LoadEntityActionsRequested", EntityActionRequested: "EntityActionRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 14, vars: 16, consts: [["gridContainer", ""], [1, "mj-grid-toolbar"], [1, "mj-aggregate-cards"], [1, "mj-grid-content"], [1, "mj-grid-loading-overlay"], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load this data", "ActionText", "Retry", "ActionIcon", "fa-solid fa-arrows-rotate", 1, "mj-grid-state-fill", 3, "Size", "Message"], ["Icon", "fa-solid fa-inbox", "Title", "No data to display", 1, "mj-grid-state-fill", 3, "Size"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize"], [3, "TotalRowCount", "PageNumber", "PageSize", "IsLoading"], [1, "mj-aggregate-summary"], [3, "closed", "visible", "config"], [3, "DriverClass", "Context"], [1, "toolbar-left"], [1, "toolbar-search"], [1, "toolbar-center"], [1, "row-count"], [1, "selection-count"], [1, "toolbar-right"], [3, "EntityName"], ["title", "Create new record", 1, "toolbar-button"], ["title", "Refresh data", 1, "toolbar-button", 3, "disabled"], ["title", "Export", 1, "toolbar-button", 3, "disabled"], ["title", "Delete selected records", 1, "toolbar-button", "toolbar-button-danger"], ["title", "Compare selected records", 1, "toolbar-button", 3, "disabled"], ["title", "Merge selected records", 1, "toolbar-button", 3, "disabled"], [1, "toolbar-button", 3, "title", "disabled"], ["title", "Search for duplicate records", 1, "toolbar-button", 3, "disabled"], ["title", "Send message to selected records", 1, "toolbar-button", 3, "disabled"], ["title", "Add New", 1, "toolbar-button"], ["title", "Refresh", 1, "toolbar-button", 3, "disabled"], ["title", "Delete Selected", 1, "toolbar-button", "toolbar-button-danger"], ["title", "Export", 1, "toolbar-button"], ["title", "Column Chooser", 1, "toolbar-button"], [1, "toolbar-overflow"], [1, "fa-solid", "fa-search", "search-icon"], ["type", "text", 1, "search-input", 3, "input", "placeholder", "value"], ["aria-label", "Clear search", 1, "search-clear"], ["aria-label", "Clear search", 1, "search-clear", 3, "click"], [1, "fa-solid", "fa-times"], [1, "toolbar-button", 3, "class", "disabled", "title"], [1, "toolbar-button", 3, "click", "disabled", "title"], [3, "class"], ["title", "Create new record", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "button-text"], ["title", "Refresh data", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-arrows-rotate"], ["title", "Export", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid"], ["title", "Delete selected records", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], [1, "fa-solid", "fa-trash"], ["title", "Compare selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-compare"], ["title", "Merge selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-merge"], [1, "toolbar-button", 3, "click", "title", "disabled"], ["title", "Search for duplicate records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-magnifying-glass-plus"], ["title", "Send message to selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-envelope"], ["title", "Add New", 1, "toolbar-button", 3, "click"], ["title", "Refresh", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-refresh"], ["title", "Delete Selected", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], ["title", "Export", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-download"], ["title", "Column Chooser", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-columns"], [1, "toolbar-overflow", 3, "click"], ["title", "More actions", 1, "toolbar-button", "overflow-trigger", 3, "click"], [1, "fa-solid", "fa-ellipsis-vertical"], [1, "overflow-menu"], [1, "overflow-item"], [1, "overflow-divider"], [1, "overflow-item", 3, "click"], [1, "fa-solid", "fa-file-excel"], [1, "overflow-section-label"], [1, "overflow-item", 3, "disabled"], [1, "overflow-item", 3, "click", "disabled"], [1, "aggregate-card"], [1, "aggregate-card-loading"], [1, "aggregate-card-icon"], [1, "aggregate-card-content"], [1, "aggregate-card-label"], [1, "aggregate-card-value"], [1, "fa-solid", "fa-spinner", "fa-spin"], ["text", "Loading..."], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load this data", "ActionText", "Retry", "ActionIcon", "fa-solid fa-arrows-rotate", 1, "mj-grid-state-fill", 3, "Action", "Size", "Message"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "cellClicked", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "cellClicked", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize"], [3, "PageChange", "TotalRowCount", "PageNumber", "PageSize", "IsLoading"], [1, "aggregate-summary-content"], [1, "aggregate-summary-item"], [1, "aggregate-loading"], [1, "agg-summary-label"], [1, "agg-summary-value"], [3, "Completed", "Cancelled", "DriverClass", "Context"]], template: function EntityDataGridComponent_Template(rf, ctx) { if (rf & 1) {
|
|
5301
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.GridContainer = _t.first);
|
|
5302
|
+
} }, inputs: { Params: "Params", AllowLoad: "AllowLoad", AutoRefreshOnParamsChange: "AutoRefreshOnParamsChange", PaginationMode: "PaginationMode", PageSize: "PageSize", CacheBlockSize: "CacheBlockSize", MaxBlocksInCache: "MaxBlocksInCache", ShowPager: "ShowPager", ExportDataProvider: "ExportDataProvider", ShowRecycleBin: "ShowRecycleBin", PagerPageNumber: "PagerPageNumber", TotalRowCount: "TotalRowCount", Data: "Data", Columns: "Columns", GridState: "GridState", AllowColumnReorder: "AllowColumnReorder", AllowColumnResize: "AllowColumnResize", AllowColumnToggle: "AllowColumnToggle", ShowHeader: "ShowHeader", AllowSorting: "AllowSorting", AllowMultiSort: "AllowMultiSort", ServerSideSorting: "ServerSideSorting", AllowColumnFilters: "AllowColumnFilters", ShowSearch: "ShowSearch", SelectionMode: "SelectionMode", SelectedKeys: "SelectedKeys", KeyField: "KeyField", EditMode: "EditMode", AllowAdd: "AllowAdd", AllowDelete: "AllowDelete", Height: "Height", RowHeight: "RowHeight", WrapText: "WrapText", VirtualScroll: "VirtualScroll", ShowRowNumbers: "ShowRowNumbers", FillWidth: "FillWidth", Striped: "Striped", GridLines: "GridLines", VisualConfig: "VisualConfig", ShowToolbar: "ShowToolbar", ToolbarConfig: "ToolbarConfig", StateKey: "StateKey", AutoPersistState: "AutoPersistState", StatePersistDebounce: "StatePersistDebounce", RefreshDebounce: "RefreshDebounce", FilterText: "FilterText", ShowNewButton: "ShowNewButton", ShowRefreshButton: "ShowRefreshButton", ShowExportButton: "ShowExportButton", ShowDeleteButton: "ShowDeleteButton", ShowCompareButton: "ShowCompareButton", ShowMergeButton: "ShowMergeButton", ShowAddToListButton: "ShowAddToListButton", ShowDuplicateSearchButton: "ShowDuplicateSearchButton", ShowCommunicationButton: "ShowCommunicationButton", AutoNavigate: "AutoNavigate", NavigateOnDoubleClick: "NavigateOnDoubleClick", CreateRecordMode: "CreateRecordMode", NewRecordValues: "NewRecordValues", ShowEntityActionButtons: "ShowEntityActionButtons", EntityActions: "EntityActions", AutoLoadEntityActions: "AutoLoadEntityActions", AggregatesConfig: "AggregatesConfig" }, outputs: { PageChange: "PageChange", AggregatesLoaded: "AggregatesLoaded", BeforeRowSelect: "BeforeRowSelect", AfterRowSelect: "AfterRowSelect", BeforeRowDeselect: "BeforeRowDeselect", AfterRowDeselect: "AfterRowDeselect", SelectionChange: "SelectionChange", BeforeRowClick: "BeforeRowClick", AfterRowClick: "AfterRowClick", BeforeRowDoubleClick: "BeforeRowDoubleClick", AfterRowDoubleClick: "AfterRowDoubleClick", ForeignKeyClick: "ForeignKeyClick", BeforeCellEdit: "BeforeCellEdit", AfterCellEditBegin: "AfterCellEditBegin", BeforeCellEditCommit: "BeforeCellEditCommit", AfterCellEditCommit: "AfterCellEditCommit", BeforeCellEditCancel: "BeforeCellEditCancel", AfterCellEditCancel: "AfterCellEditCancel", BeforeRowSave: "BeforeRowSave", AfterRowSave: "AfterRowSave", BeforeRowDelete: "BeforeRowDelete", AfterRowDelete: "AfterRowDelete", BeforeDataLoad: "BeforeDataLoad", AfterDataLoad: "AfterDataLoad", BeforeDataRefresh: "BeforeDataRefresh", AfterDataRefresh: "AfterDataRefresh", BeforeSort: "BeforeSort", AfterSort: "AfterSort", BeforeColumnReorder: "BeforeColumnReorder", AfterColumnReorder: "AfterColumnReorder", BeforeColumnResize: "BeforeColumnResize", AfterColumnResize: "AfterColumnResize", BeforeColumnVisibilityChange: "BeforeColumnVisibilityChange", AfterColumnVisibilityChange: "AfterColumnVisibilityChange", GridStateChanged: "GridStateChanged", AddRequested: "AddRequested", DeleteRequested: "DeleteRequested", ExportRequested: "ExportRequested", NewButtonClick: "NewButtonClick", RefreshButtonClick: "RefreshButtonClick", ExportButtonClick: "ExportButtonClick", DeleteButtonClick: "DeleteButtonClick", CompareButtonClick: "CompareButtonClick", MergeButtonClick: "MergeButtonClick", AddToListButtonClick: "AddToListButtonClick", DuplicateSearchButtonClick: "DuplicateSearchButtonClick", CommunicationButtonClick: "CommunicationButtonClick", ManageColumnsRequested: "ManageColumnsRequested", NavigationRequested: "NavigationRequested", NewRecordDialogRequested: "NewRecordDialogRequested", NewRecordTabRequested: "NewRecordTabRequested", CompareRecordsRequested: "CompareRecordsRequested", MergeRecordsRequested: "MergeRecordsRequested", CommunicationRequested: "CommunicationRequested", DuplicateSearchRequested: "DuplicateSearchRequested", AddToListRequested: "AddToListRequested", LoadEntityActionsRequested: "LoadEntityActionsRequested", EntityActionRequested: "EntityActionRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 14, vars: 16, consts: [["gridContainer", ""], [1, "mj-grid-toolbar"], [1, "mj-aggregate-cards"], [1, "mj-grid-content"], [1, "mj-grid-loading-overlay"], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load this data", "ActionText", "Retry", "ActionIcon", "fa-solid fa-arrows-rotate", 1, "mj-grid-state-fill", 3, "Size", "Message"], ["Icon", "fa-solid fa-inbox", 1, "mj-grid-state-fill", 3, "Size", "Title"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize"], [3, "TotalRowCount", "PageNumber", "PageSize", "IsLoading"], [1, "mj-aggregate-summary"], [3, "closed", "visible", "config"], [3, "DriverClass", "Context"], [1, "toolbar-left"], [1, "toolbar-search"], [1, "toolbar-center"], [1, "row-count"], [1, "selection-count"], [1, "toolbar-right"], [3, "EntityName"], ["title", "Create new record", 1, "toolbar-button"], ["title", "Refresh data", 1, "toolbar-button", 3, "disabled"], ["title", "Export", 1, "toolbar-button", 3, "disabled"], ["title", "Delete selected records", 1, "toolbar-button", "toolbar-button-danger"], ["title", "Compare selected records", 1, "toolbar-button", 3, "disabled"], ["title", "Merge selected records", 1, "toolbar-button", 3, "disabled"], [1, "toolbar-button", 3, "title", "disabled"], ["title", "Search for duplicate records", 1, "toolbar-button", 3, "disabled"], ["title", "Send message to selected records", 1, "toolbar-button", 3, "disabled"], ["title", "Add New", 1, "toolbar-button"], ["title", "Refresh", 1, "toolbar-button", 3, "disabled"], ["title", "Delete Selected", 1, "toolbar-button", "toolbar-button-danger"], ["title", "Export", 1, "toolbar-button"], ["title", "Column Chooser", 1, "toolbar-button"], [1, "toolbar-overflow"], [1, "fa-solid", "fa-search", "search-icon"], ["type", "text", 1, "search-input", 3, "input", "placeholder", "value"], ["aria-label", "Clear search", 1, "search-clear"], ["aria-label", "Clear search", 1, "search-clear", 3, "click"], [1, "fa-solid", "fa-times"], [1, "toolbar-button", 3, "class", "disabled", "title"], [1, "toolbar-button", 3, "click", "disabled", "title"], [3, "class"], ["title", "Create new record", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "button-text"], ["title", "Refresh data", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-arrows-rotate"], ["title", "Export", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid"], ["title", "Delete selected records", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], [1, "fa-solid", "fa-trash"], ["title", "Compare selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-compare"], ["title", "Merge selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-code-merge"], [1, "toolbar-button", 3, "click", "title", "disabled"], ["title", "Search for duplicate records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-magnifying-glass-plus"], ["title", "Send message to selected records", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-envelope"], ["title", "Add New", 1, "toolbar-button", 3, "click"], ["title", "Refresh", 1, "toolbar-button", 3, "click", "disabled"], [1, "fa-solid", "fa-refresh"], ["title", "Delete Selected", 1, "toolbar-button", "toolbar-button-danger", 3, "click"], ["title", "Export", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-download"], ["title", "Column Chooser", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-columns"], [1, "toolbar-overflow", 3, "click"], ["title", "More actions", 1, "toolbar-button", "overflow-trigger", 3, "click"], [1, "fa-solid", "fa-ellipsis-vertical"], [1, "overflow-menu"], [1, "overflow-item"], [1, "overflow-divider"], [1, "overflow-item", 3, "click"], [1, "fa-solid", "fa-file-excel"], [1, "overflow-section-label"], [1, "overflow-item", 3, "disabled"], [1, "overflow-item", 3, "click", "disabled"], [1, "aggregate-card"], [1, "aggregate-card-loading"], [1, "aggregate-card-icon"], [1, "aggregate-card-content"], [1, "aggregate-card-label"], [1, "aggregate-card-value"], [1, "fa-solid", "fa-spinner", "fa-spin"], ["text", "Loading..."], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load this data", "ActionText", "Retry", "ActionIcon", "fa-solid fa-arrows-rotate", 1, "mj-grid-state-fill", 3, "Action", "Size", "Message"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "cellClicked", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "rowData", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight"], [1, "mj-ag-grid", "ag-theme-alpine", 3, "gridReady", "cellClicked", "rowClicked", "rowDoubleClicked", "sortChanged", "selectionChanged", "columnResized", "columnMoved", "theme", "columnDefs", "defaultColDef", "rowSelection", "getRowId", "suppressCellFocus", "rowHeight", "headerHeight", "rowModelType", "cacheBlockSize", "maxBlocksInCache", "infiniteInitialRowCount", "cacheOverflowSize"], [3, "PageChange", "TotalRowCount", "PageNumber", "PageSize", "IsLoading"], [1, "aggregate-summary-content"], [1, "aggregate-summary-item"], [1, "aggregate-loading"], [1, "agg-summary-label"], [1, "agg-summary-value"], [3, "Completed", "Cancelled", "DriverClass", "Context"]], template: function EntityDataGridComponent_Template(rf, ctx) { if (rf & 1) {
|
|
5009
5303
|
i0.ɵɵelementStart(0, "div", null, 0);
|
|
5010
5304
|
i0.ɵɵconditionalCreate(2, EntityDataGridComponent_Conditional_2_Template, 27, 19, "div", 1);
|
|
5011
5305
|
i0.ɵɵconditionalCreate(3, EntityDataGridComponent_Conditional_3_Template, 4, 1, "div", 2);
|
|
5012
5306
|
i0.ɵɵelementStart(4, "div", 3);
|
|
5013
5307
|
i0.ɵɵconditionalCreate(5, EntityDataGridComponent_Conditional_5_Template, 2, 0, "div", 4);
|
|
5014
5308
|
i0.ɵɵconditionalCreate(6, EntityDataGridComponent_Conditional_6_Template, 1, 2, "mj-empty-state", 5);
|
|
5015
|
-
i0.ɵɵconditionalCreate(7, EntityDataGridComponent_Conditional_7_Template, 1,
|
|
5309
|
+
i0.ɵɵconditionalCreate(7, EntityDataGridComponent_Conditional_7_Template, 1, 2, "mj-empty-state", 6);
|
|
5016
5310
|
i0.ɵɵconditionalCreate(8, EntityDataGridComponent_Conditional_8_Template, 1, 9, "ag-grid-angular", 7);
|
|
5017
5311
|
i0.ɵɵconditionalCreate(9, EntityDataGridComponent_Conditional_9_Template, 1, 13, "ag-grid-angular", 8);
|
|
5018
5312
|
i0.ɵɵelementEnd();
|
|
@@ -5072,7 +5366,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
5072
5366
|
animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
|
|
5073
5367
|
])
|
|
5074
5368
|
])
|
|
5075
|
-
], template: "<!-- Grid Container -->\n<div\n #gridContainer\n [class]=\"gridContainerClasses.join(' ')\"\n [style.height]=\"gridHeightStyle\">\n\n <!-- Toolbar -->\n @if (ShowToolbar) {\n <div class=\"mj-grid-toolbar\">\n <div class=\"toolbar-left\">\n <!-- Search -->\n @if (ShowSearch) {\n <div class=\"toolbar-search\">\n <i class=\"fa-solid fa-search search-icon\"></i>\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"ToolbarConfig.searchPlaceholder || 'Search...'\"\n [value]=\"FilterText\"\n (input)=\"FilterText = $any($event.target).value\" />\n @if (FilterText) {\n <button\n class=\"search-clear\"\n (click)=\"FilterText = ''\"\n aria-label=\"Clear search\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n }\n <!-- Custom Left Buttons -->\n @for (button of ToolbarConfig.customButtons; track button) {\n @if (button.position !== 'right' && isButtonVisible(button)) {\n <button\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n @if (button.icon) {\n <i [class]=\"button.icon\"></i>\n }\n @if (button.text) {\n <span>{{ button.text }}</span>\n }\n </button>\n }\n }\n </div>\n <div class=\"toolbar-center\">\n <!-- Row Count -->\n @if (ToolbarConfig.showRowCount !== false) {\n <span class=\"row-count\">\n {{ totalRowCount }} {{ totalRowCount === 1 ? 'row' : 'rows' }}\n </span>\n }\n <!-- Selection Count -->\n @if (ToolbarConfig.showSelectionCount && SelectedKeys.length > 0) {\n <span class=\"selection-count\">\n ({{ SelectedKeys.length }} selected)\n </span>\n }\n </div>\n <div class=\"toolbar-right\">\n <!-- Recycle Bin chip \u2014 auto-hides when no deleted records, no tracking,\n or user lacks Delete permission. -->\n @if (ShowRecycleBin) {\n <mj-recycle-bin-chip [EntityName]=\"entityInfoName\"></mj-recycle-bin-chip>\n }\n <!-- New/Add Button (predefined) -->\n @if (ShowNewButton) {\n <button\n class=\"toolbar-button\"\n title=\"Create new record\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n <span class=\"button-text\">New</span>\n </button>\n }\n <!-- Refresh Button (predefined) -->\n @if (ShowRefreshButton) {\n <button\n class=\"toolbar-button\"\n title=\"Refresh data\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-arrows-rotate\" [class.fa-spin]=\"loading\"></i>\n <span class=\"button-text\">Refresh</span>\n </button>\n }\n <!-- Export Button (predefined) -->\n @if (ShowExportButton) {\n <button\n class=\"toolbar-button\"\n title=\"Export\"\n [disabled]=\"IsPreparingExport\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid\" [class.fa-file-excel]=\"!IsPreparingExport\" [class.fa-spinner]=\"IsPreparingExport\" [class.fa-spin]=\"IsPreparingExport\"></i>\n <span class=\"button-text\">Export</span>\n </button>\n }\n <!-- Delete Button (predefined) -->\n @if (ShowDeleteButton && HasSelection) {\n <button\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete selected records\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"button-text\">Delete</span>\n </button>\n }\n <!-- Compare Button (predefined) -->\n @if (ShowCompareButton) {\n <button\n class=\"toolbar-button\"\n title=\"Compare selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onCompareClick()\">\n <i class=\"fa-solid fa-code-compare\"></i>\n <span class=\"button-text\">Compare</span>\n </button>\n }\n <!-- Merge Button (predefined) -->\n @if (ShowMergeButton) {\n <button\n class=\"toolbar-button\"\n title=\"Merge selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onMergeClick()\">\n <i class=\"fa-solid fa-code-merge\"></i>\n <span class=\"button-text\">Merge</span>\n </button>\n }\n <!-- Add to List Button (predefined) -->\n @if (ShowAddToListButton) {\n <button\n class=\"toolbar-button\"\n [title]=\"HasSelection ? 'Add selected records to a list' : 'Add all matching records to a list'\"\n [disabled]=\"(!HasSelection && !ExportDataProvider) || IsPreparingAddToList\"\n (click)=\"onAddToListClick()\">\n <i class=\"fa-solid\" [class.fa-list-check]=\"!IsPreparingAddToList\" [class.fa-spinner]=\"IsPreparingAddToList\" [class.fa-spin]=\"IsPreparingAddToList\"></i>\n <span class=\"button-text\">Add to List</span>\n </button>\n }\n <!-- Search for Duplicates Button (predefined) -->\n @if (ShowDuplicateSearchButton) {\n <button\n class=\"toolbar-button\"\n title=\"Search for duplicate records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onDuplicateSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass-plus\"></i>\n <span class=\"button-text\">Find Duplicates</span>\n </button>\n }\n <!-- Communication Button (predefined) -->\n @if (ShowCommunicationButton && EntitySupportsCommunication) {\n <button\n class=\"toolbar-button\"\n title=\"Send message to selected records\"\n [disabled]=\"!HasSelection\"\n (click)=\"onCommunicationClick()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span class=\"button-text\">Send Message</span>\n </button>\n }\n <!-- Legacy ToolbarConfig buttons -->\n <!-- Add Button (legacy) -->\n @if (ToolbarConfig.showAdd && AllowAdd && !ShowNewButton) {\n <button\n class=\"toolbar-button\"\n title=\"Add New\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n </button>\n }\n <!-- Refresh Button (legacy) -->\n @if (ToolbarConfig.showRefresh !== false && !ShowRefreshButton) {\n <button\n class=\"toolbar-button\"\n title=\"Refresh\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-refresh\" [class.fa-spin]=\"loading\"></i>\n </button>\n }\n <!-- Delete Button (legacy) -->\n @if (ToolbarConfig.showDelete && AllowDelete && HasSelection && !ShowDeleteButton) {\n <button\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete Selected\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n }\n <!-- Export Button (legacy) -->\n @if (ToolbarConfig.showExport && !ShowExportButton) {\n <button\n class=\"toolbar-button\"\n title=\"Export\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid fa-download\"></i>\n </button>\n }\n <!-- Column Chooser Button -->\n @if (ToolbarConfig.showColumnChooser && AllowColumnToggle) {\n <button\n class=\"toolbar-button\"\n title=\"Column Chooser\"\n (click)=\"onColumnChooserClick()\">\n <i class=\"fa-solid fa-columns\"></i>\n </button>\n }\n <!-- Custom Right Buttons -->\n @for (button of ToolbarConfig.customButtons; track button) {\n @if (button.position === 'right' && isButtonVisible(button)) {\n <button\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n @if (button.icon) {\n <i [class]=\"button.icon\"></i>\n }\n @if (button.text) {\n <span>{{ button.text }}</span>\n }\n </button>\n }\n }\n <!-- Overflow Menu -->\n @if (hasOverflowMenuItems) {\n <div class=\"toolbar-overflow\" (click)=\"$event.stopPropagation()\">\n <button\n class=\"toolbar-button overflow-trigger\"\n title=\"More actions\"\n (click)=\"toggleOverflowMenu()\">\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </button>\n @if (showOverflowMenu) {\n <div class=\"overflow-menu\" [@fadeIn]>\n <!-- Export (if in overflow) -->\n @if (showExportInOverflow) {\n <button class=\"overflow-item\" (click)=\"onExportClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-file-excel\"></i>\n <span>Export</span>\n </button>\n }\n <!-- Entity Actions -->\n @if (ShowEntityActionButtons && EntityActions.length > 0) {\n <div class=\"overflow-divider\"></div>\n <div class=\"overflow-section-label\">Actions</div>\n @for (action of EntityActions; track action) {\n <button\n class=\"overflow-item\"\n [disabled]=\"!isEntityActionEnabled(action)\"\n (click)=\"onEntityActionClick(action); closeOverflowMenu()\">\n <i [class]=\"action.icon || 'fa-solid fa-bolt'\"></i>\n <span>{{ action.name }}</span>\n </button>\n }\n }\n <!-- Column Chooser (if in overflow) -->\n @if (showColumnChooserInOverflow) {\n <div class=\"overflow-divider\"></div>\n }\n @if (showColumnChooserInOverflow) {\n <button class=\"overflow-item\" (click)=\"onColumnChooserClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-columns\"></i>\n <span>Manage Columns</span>\n </button>\n }\n <!-- Selection-dependent actions in overflow -->\n @if (HasSelection && hasSelectionDependentOverflowActions) {\n <div class=\"overflow-divider\"></div>\n @if (showCommunicationInOverflow) {\n <button class=\"overflow-item\" (click)=\"onCommunicationClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span>Send Message</span>\n </button>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Aggregate Cards (displayed as summary cards above the grid) -->\n @if (ShowAggregatePanel) {\n <div class=\"mj-aggregate-cards\">\n @for (agg of CardAggregates; track agg) {\n <div class=\"aggregate-card\">\n @if (agg.icon) {\n <div class=\"aggregate-card-icon\">\n <i [class]=\"agg.icon\"></i>\n </div>\n }\n <div class=\"aggregate-card-content\">\n <span class=\"aggregate-card-label\">{{ agg.label }}</span>\n <span class=\"aggregate-card-value\">{{ getAggregateValue(agg) }}</span>\n </div>\n </div>\n }\n @if (AggregatesLoading) {\n <div class=\"aggregate-card-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </div>\n }\n </div>\n }\n\n <!-- Grid Content -->\n <div class=\"mj-grid-content\">\n <!-- Loading Overlay -->\n @if (loading && rowData.length === 0) {\n <div class=\"mj-grid-loading-overlay\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (errorMessage && !loading) {\n <mj-empty-state\n class=\"mj-grid-state-fill\"\n [Size]=\"EmptyStateSize\"\n Variant=\"error\"\n Icon=\"fa-solid fa-triangle-exclamation\"\n Title=\"Couldn't load this data\"\n [Message]=\"FriendlyErrorMessage\"\n ActionText=\"Retry\"\n ActionIcon=\"fa-solid fa-arrows-rotate\"\n (Action)=\"Refresh()\" />\n }\n\n <!-- Empty State -->\n @if (!loading && !errorMessage && rowData.length === 0) {\n <mj-empty-state\n class=\"mj-grid-state-fill\"\n [Size]=\"EmptyStateSize\"\n Icon=\"fa-solid fa-inbox\"\n Title=\"No data to display\" />\n }\n\n <!-- AG Grid (Client-side mode) -->\n @if (!errorMessage && PaginationMode === 'client' && (rowData.length > 0 || loading)) {\n <ag-grid-angular\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [rowData]=\"rowData\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n (gridReady)=\"onGridReady($event)\"\n (cellClicked)=\"onAgCellClicked($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n }\n\n <!-- AG Grid (Infinite Scroll mode) -->\n @if (!errorMessage && PaginationMode === 'infinite') {\n <ag-grid-angular\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n [rowModelType]=\"'infinite'\"\n [cacheBlockSize]=\"CacheBlockSize\"\n [maxBlocksInCache]=\"MaxBlocksInCache\"\n [infiniteInitialRowCount]=\"1\"\n [cacheOverflowSize]=\"2\"\n (gridReady)=\"onGridReady($event)\"\n (cellClicked)=\"onAgCellClicked($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n }\n\n </div>\n\n <!-- Pagination (opt-in via ShowPager input) -->\n @if (ShowPager) {\n <mj-pagination\n [TotalRowCount]=\"totalRowCount\"\n [PageNumber]=\"PagerPageNumber\"\n [PageSize]=\"PageSize\"\n [IsLoading]=\"loading\"\n (PageChange)=\"PageChange.emit($event)\">\n </mj-pagination>\n }\n\n <!-- Aggregate Summary Row (outside mj-grid-content so it's always visible) -->\n @if (ShowAggregateSummary) {\n <div class=\"mj-aggregate-summary\">\n <div class=\"aggregate-summary-content\">\n @for (agg of ColumnAggregates; track agg) {\n <div class=\"aggregate-summary-item\">\n @if (agg.icon) {\n <i [class]=\"agg.icon\"></i>\n }\n <span class=\"agg-summary-label\">{{ agg.label }}:</span>\n <span class=\"agg-summary-value\">{{ getAggregateValue(agg) }}</span>\n </div>\n }\n </div>\n @if (AggregatesLoading) {\n <div class=\"aggregate-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </div>\n }\n </div>\n }\n</div>\n\n<!-- Export Dialog -->\n<mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n</mj-export-dialog>\n\n<!-- Runtime-UX driver for an entity action that names a RuntimeUXDriverClass (e.g. the Record Process\n bulk-update runner). Mounted in-place over the grid \u2014 no parent wiring required. -->\n@if (ActiveRuntimeDriver) {\n <mj-entity-action-ux-host\n [DriverClass]=\"ActiveRuntimeDriver.DriverClass\"\n [Context]=\"ActiveRuntimeDriver.Context\"\n (Completed)=\"OnRuntimeDriverCompleted($event)\"\n (Cancelled)=\"OnRuntimeDriverCancelled()\">\n </mj-entity-action-ux-host>\n}\n", styles: ["/* ========================================\n CSS Custom Properties (Theme Variables)\n ======================================== */\n\n:host {\n /* Grid container */\n --grid-border-color: var(--mj-border-default);\n --grid-border-radius: 0px;\n --grid-background: var(--mj-bg-surface);\n\n /* Header */\n --grid-header-bg: var(--mj-bg-surface-card);\n --grid-header-text: var(--mj-text-primary);\n --grid-header-font-weight: 600;\n --grid-header-height: 40px;\n --grid-header-border-color: var(--mj-border-default);\n\n /* Rows */\n --grid-row-height: 40px;\n --grid-row-bg: var(--mj-bg-surface);\n --grid-row-bg-alt: var(--mj-bg-surface-card);\n --grid-row-hover-bg: var(--mj-bg-surface-hover);\n --grid-row-selected-bg: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n --grid-row-selected-hover-bg: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n\n /* Cells */\n --grid-cell-padding: 8px 12px;\n --grid-cell-text: var(--mj-text-primary);\n --grid-cell-border-color: var(--mj-border-subtle);\n\n /* Selection - mellow yellow to avoid conflict with blue hyperlinks */\n --grid-checkbox-color: var(--mj-brand-primary);\n --grid-selection-indicator-color: var(--mj-status-warning);\n\n /* Editing */\n --grid-edit-cell-bg: var(--mj-bg-surface);\n --grid-edit-cell-border: var(--mj-brand-primary);\n --grid-edit-cell-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n\n /* Sorting */\n --grid-sort-indicator-color: var(--mj-brand-primary);\n\n /* Toolbar */\n --grid-toolbar-bg: var(--mj-bg-surface);\n --grid-toolbar-height: 48px;\n --grid-toolbar-border-color: var(--mj-border-default);\n\n /* Loading */\n --grid-loading-overlay-bg: color-mix(in srgb, var(--mj-bg-surface) 80%, transparent);\n\n /* Empty state */\n --grid-empty-text-color: var(--mj-text-disabled);\n --grid-empty-icon-color: var(--mj-border-strong);\n\n display: block;\n height: 100%;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n}\n\n/* ========================================\n Grid Container\n ======================================== */\n\n.mj-grid-container {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--grid-border-color);\n border-radius: var(--grid-border-radius);\n background: var(--grid-background);\n overflow: hidden;\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.mj-grid-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: var(--grid-toolbar-height);\n padding: 0 12px;\n background: var(--grid-toolbar-bg);\n border-bottom: 1px solid var(--grid-toolbar-border-color);\n gap: 12px;\n}\n\n.toolbar-left,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-center {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-secondary);\n font-size: 13px;\n}\n\n.toolbar-search {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.search-icon {\n position: absolute;\n left: 10px;\n color: var(--mj-text-disabled);\n font-size: 13px;\n}\n\n.search-input {\n padding: 6px 30px 6px 32px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n font-size: 13px;\n width: 200px;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.search-input:focus {\n outline: none;\n border-color: var(--grid-selection-indicator-color);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.search-clear {\n position: absolute;\n right: 6px;\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--mj-text-disabled);\n font-size: 12px;\n}\n\n.search-clear:hover {\n color: var(--mj-text-secondary);\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background-color 0.2s, border-color 0.2s;\n}\n\n.toolbar-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.toolbar-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n.toolbar-button-danger {\n color: var(--mj-status-error);\n}\n\n.toolbar-button-danger:hover:not(:disabled) {\n background: var(--mj-status-error-bg);\n border-color: var(--mj-status-error);\n}\n\n.row-count,\n.selection-count {\n font-weight: 500;\n}\n\n/* ========================================\n Grid Content\n ======================================== */\n\n.mj-grid-content {\n flex: 1;\n position: relative;\n overflow: hidden;\n}\n\n.mj-grid-scroll-container {\n height: 100%;\n overflow: auto;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.mj-grid-loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--grid-loading-overlay-bg);\n z-index: 10;\n}\n\n/* ========================================\n Error State\n ======================================== */\n\n/* ========================================\n Empty / Error State\n ======================================== */\n\n/* Flex-fill so <mj-empty-state> placeholders occupy the grid content area\n (the host centers its content but does not grow to fill the parent). */\n.mj-grid-state-fill {\n flex: 1 1 auto;\n}\n\n/* ========================================\n Header Row\n ======================================== */\n\n.mj-grid-header {\n display: flex;\n min-height: var(--grid-header-height);\n background: var(--grid-header-bg);\n border-bottom: 2px solid var(--grid-header-border-color);\n position: sticky;\n top: 0;\n z-index: 5;\n}\n\n.mj-grid-header-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n font-weight: var(--grid-header-font-weight);\n color: var(--grid-header-text);\n font-size: 13px;\n user-select: none;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right: 1px solid var(--grid-cell-border-color);\n flex-shrink: 0;\n}\n\n.mj-grid-header-cell:last-child {\n border-right: none;\n}\n\n.mj-grid-header-cell.sortable {\n cursor: pointer;\n}\n\n.mj-grid-header-cell.sortable:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n.header-text {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sort-indicator {\n display: flex;\n align-items: center;\n margin-left: 6px;\n color: var(--grid-sort-indicator-color);\n}\n\n.sort-indicator i {\n font-size: 12px;\n}\n\n.sort-index {\n font-size: 10px;\n margin-left: 2px;\n font-weight: normal;\n}\n\n/* ========================================\n Data Rows\n ======================================== */\n\n.mj-grid-row {\n display: flex;\n align-items: stretch;\n background: var(--grid-row-bg);\n transition: background-color 0.15s;\n cursor: default;\n}\n\n.mj-grid-row:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-alt {\n background: var(--grid-row-bg-alt);\n}\n\n.mj-grid-row.grid-row-alt:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-selected {\n background: var(--grid-row-selected-bg);\n}\n\n.mj-grid-row.grid-row-selected:hover {\n background: var(--grid-row-selected-hover-bg);\n}\n\n.mj-grid-row.grid-row-editing {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, var(--mj-bg-surface));\n}\n\n.mj-grid-row.grid-row-dirty {\n border-left: 3px solid var(--mj-status-warning);\n}\n\n/* ========================================\n Data Cells\n ======================================== */\n\n.mj-grid-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n color: var(--grid-cell-text);\n font-size: 13px;\n overflow: hidden;\n border-right: 1px solid transparent;\n flex-shrink: 0;\n}\n\n.mj-grid-cell:last-child {\n border-right: none;\n}\n\n/* Grid lines modes */\n.grid-lines-horizontal .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-vertical .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n/* Cell alignment */\n.mj-grid-cell.align-left {\n justify-content: flex-start;\n}\n\n.mj-grid-cell.align-center {\n justify-content: center;\n}\n\n.mj-grid-cell.align-right {\n justify-content: flex-end;\n}\n\n.cell-content {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Special cells */\n.row-number-cell {\n width: 50px;\n min-width: 50px;\n max-width: 50px;\n justify-content: center;\n color: var(--mj-text-disabled);\n font-size: 12px;\n background: var(--grid-header-bg);\n}\n\n.checkbox-cell {\n width: 40px;\n min-width: 40px;\n max-width: 40px;\n justify-content: center;\n}\n\n.checkbox-cell input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--grid-checkbox-color);\n}\n\n/* ========================================\n Virtual Scrolling\n ======================================== */\n\n.mj-grid-virtual-spacer {\n flex-shrink: 0;\n}\n\n/* ========================================\n Responsive Adjustments\n ======================================== */\n\n@media (max-width: 768px) {\n .mj-grid-toolbar {\n flex-wrap: wrap;\n padding: 8px;\n }\n\n .toolbar-search {\n order: 3;\n width: 100%;\n margin-top: 8px;\n }\n\n .search-input {\n width: 100%;\n }\n\n .toolbar-center {\n order: 2;\n }\n\n /* Hide button text on mobile */\n .toolbar-button .button-text {\n display: none;\n }\n}\n\n/* ========================================\n Toolbar Button Text\n ======================================== */\n\n.toolbar-button .button-text {\n font-size: 13px;\n}\n\n/* ========================================\n Overflow Menu\n ======================================== */\n\n.toolbar-overflow {\n position: relative;\n}\n\n.overflow-trigger {\n padding: 6px 8px !important;\n}\n\n.overflow-trigger i {\n font-size: 16px;\n}\n\n.overflow-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n min-width: 220px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);\n z-index: 1000;\n overflow: hidden;\n}\n\n.overflow-item {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 10px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 14px;\n color: var(--mj-text-primary);\n text-align: left;\n gap: 12px;\n transition: background-color 0.15s;\n}\n\n.overflow-item:hover:not(:disabled) {\n background: var(--mj-bg-surface-card);\n}\n\n.overflow-item:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.overflow-item i {\n width: 18px;\n font-size: 14px;\n color: var(--mj-text-secondary);\n text-align: center;\n}\n\n.overflow-item span {\n flex: 1;\n}\n\n.overflow-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n.overflow-section-label {\n padding: 8px 16px 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--mj-text-disabled);\n letter-spacing: 0.5px;\n}\n\n/* Entity Actions submenu styling */\n.overflow-item.action-item i {\n color: var(--mj-brand-primary);\n}\n\n/* ========================================\n Highlight Matches\n ======================================== */\n\n::ng-deep .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n padding: 0 1px;\n}\n\n/* ========================================\n AG Grid Customizations\n ======================================== */\n\n.mj-ag-grid {\n width: 100%;\n height: 100%;\n}\n\n/* AG Grid v35 uses JS-based theming (colorSchemeVariable) \u2014 no CSS variable overrides needed */\n\n/* Selected row styling - left indicator bar only, background handled by AG Grid theme */\n::ng-deep .ag-row-selected {\n box-shadow: inset 4px 0 0 0 var(--mj-status-warning);\n}\n\n/* Selection checkbox styling */\n::ng-deep .ag-checkbox-input-wrapper {\n width: 18px;\n height: 18px;\n}\n\n::ng-deep .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--grid-checkbox-color, var(--mj-brand-primary));\n}\n\n/* ========================================\n Visual Config: Header Styles\n ======================================== */\n\n/* Flat header (minimal) */\n.header-style-flat ::ng-deep .ag-header {\n background: var(--grid-header-bg, var(--mj-bg-surface-card));\n border-bottom: 1px solid var(--grid-header-border-color, var(--mj-border-default));\n}\n\n/* Elevated header (default - subtle shadow) */\n.header-style-elevated ::ng-deep .ag-header {\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--grid-header-border-color, var(--mj-border-default));\n}\n\n.header-style-elevated.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);\n}\n\n/* Gradient header (more prominent) */\n.header-style-gradient ::ng-deep .ag-header {\n background: var(--mj-bg-surface-sunken);\n border-bottom: none;\n}\n\n.header-style-gradient.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n/* Bold header (high contrast) */\n.header-style-bold ::ng-deep .ag-header {\n background: var(--mj-text-secondary);\n border-bottom: none;\n}\n\n.header-style-bold ::ng-deep .ag-header-cell-text {\n color: var(--mj-text-inverse);\n font-weight: 600;\n}\n\n.header-style-bold ::ng-deep .ag-header-icon {\n color: color-mix(in srgb, var(--mj-text-inverse) 70%, transparent);\n}\n\n.header-style-bold ::ng-deep .ag-header-cell:hover {\n background: color-mix(in srgb, var(--mj-text-inverse) 10%, transparent);\n}\n\n/* Header sort icons enhancement */\n::ng-deep .ag-header-cell-sorted-asc .ag-icon-asc,\n::ng-deep .ag-header-cell-sorted-desc .ag-icon-desc {\n color: var(--grid-accent-color, var(--grid-sort-indicator-color, var(--mj-brand-primary)));\n}\n\n/* ========================================\n Visual Config: Zebra Striping\n ======================================== */\n\n/* Subtle contrast */\n.alternate-rows-subtle ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.015);\n}\n\n.alternate-rows-subtle ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, var(--mj-bg-surface-hover));\n}\n\n/* Medium contrast (default) */\n.alternate-rows-medium ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.025);\n}\n\n.alternate-rows-medium ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, var(--mj-bg-surface-hover));\n}\n\n/* Strong contrast */\n.alternate-rows-strong ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.alternate-rows-strong ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, var(--mj-bg-surface-hover));\n}\n\n/* ========================================\n Visual Config: Hover Transitions\n ======================================== */\n\n.hover-transitions ::ng-deep .ag-row {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n.hover-transitions ::ng-deep .ag-cell {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n/* ========================================\n Visual Config: Cell Padding\n ======================================== */\n\n.cell-padding-compact ::ng-deep .ag-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-compact ::ng-deep .ag-header-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-normal ::ng-deep .ag-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-normal ::ng-deep .ag-header-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-header-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n/* ========================================\n Visual Config: Checkbox Styles\n ======================================== */\n\n/* Rounded checkbox */\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper {\n border-radius: 4px;\n}\n\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper::after {\n border-radius: 3px;\n}\n\n/* Filled checkbox */\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked {\n background-color: var(--grid-checkbox-color, var(--mj-brand-primary));\n border-color: var(--grid-checkbox-color, var(--mj-brand-primary));\n}\n\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--mj-text-inverse);\n}\n\n/* ========================================\n Cell Content Formatting\n ======================================== */\n\n/* Right-aligned cells (numbers) */\n::ng-deep .cell-align-right {\n text-align: right;\n justify-content: flex-end;\n}\n\n::ng-deep .header-align-right .ag-header-cell-label {\n justify-content: flex-end;\n}\n\n/* Empty cell placeholder */\n::ng-deep .cell-empty {\n color: var(--mj-text-disabled);\n font-style: normal;\n}\n\n/* Boolean icons */\n::ng-deep .cell-boolean-true {\n color: var(--mj-status-success);\n font-size: 14px;\n}\n\n::ng-deep .cell-boolean-false {\n color: var(--mj-text-disabled);\n font-size: 14px;\n}\n\n/* Clickable links */\n::ng-deep .cell-link {\n color: var(--grid-accent-color, var(--mj-brand-primary));\n text-decoration: none;\n transition: color 0.15s;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\n\n::ng-deep .cell-link:hover {\n color: var(--mj-brand-primary);\n text-decoration: underline;\n}\n\n/* Email cells */\n::ng-deep .cell-email {\n font-family: inherit;\n font-size: 13px;\n}\n\n/* Foreign key link cells */\n::ng-deep .cell-fk-link {\n color: var(--grid-accent-color, var(--mj-brand-primary));\n text-decoration: none;\n cursor: pointer;\n transition: color 0.15s, text-decoration 0.15s;\n}\n\n::ng-deep .cell-fk-link:hover {\n color: var(--mj-brand-primary-hover);\n text-decoration: underline;\n}\n\n::ng-deep .cell-fk-link:active {\n color: var(--mj-brand-primary-active);\n}\n\n/* URL cells */\n::ng-deep .cell-url {\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n font-size: 13px;\n}\n\n/* Phone cells */\n::ng-deep .cell-phone {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n}\n\n/* ========================================\n Skeleton Loading Animation\n ======================================== */\n\n@keyframes skeleton-shimmer {\n 0% {\n background-position: -200px 0;\n }\n 100% {\n background-position: calc(200px + 100%) 0;\n }\n}\n\n.skeleton-row {\n display: flex;\n height: 40px;\n align-items: center;\n padding: 0 12px;\n border-bottom: 1px solid var(--grid-cell-border-color, var(--mj-border-subtle));\n}\n\n.skeleton-cell {\n height: 16px;\n border-radius: 4px;\n background: linear-gradient(\n 90deg,\n var(--mj-bg-surface-sunken) 0px,\n var(--mj-bg-surface-hover) 40px,\n var(--mj-bg-surface-sunken) 80px\n );\n background-size: 200px 100%;\n animation: skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-cell-short {\n width: 60px;\n}\n\n.skeleton-cell-medium {\n width: 120px;\n}\n\n.skeleton-cell-long {\n width: 180px;\n}\n\n/* Selection checkbox column header */\n::ng-deep .ag-header-select-all {\n margin-right: 0;\n}\n\n/* ========================================\n Row Border Enhancement\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-row {\n border-bottom: 1px solid var(--grid-cell-border-color, var(--mj-border-subtle));\n}\n\n::ng-deep .ag-theme-alpine .ag-row:last-child {\n border-bottom: none;\n}\n\n/* ========================================\n Focus States\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-cell-focus {\n border: none !important;\n outline: none !important;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:focus {\n outline: 2px solid var(--grid-accent-color, var(--mj-brand-primary));\n outline-offset: -2px;\n}\n\n/* ========================================\n Scrollbar Styling\n ======================================== */\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-track {\n background: var(--mj-bg-surface-card);\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb:hover {\n background: var(--mj-text-disabled);\n}\n\n/* ========================================\n Pinned Column Styling\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-pinned-left-cols-container {\n border-right: 2px solid var(--grid-border-color, var(--mj-border-default));\n}\n\n::ng-deep .ag-theme-alpine .ag-pinned-right-cols-container {\n border-left: 2px solid var(--grid-border-color, var(--mj-border-default));\n}\n\n/* ========================================\n Header Cell Enhancements\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-header-cell {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: var(--mj-text-secondary);\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell-sortable:hover .ag-header-cell-label {\n color: var(--grid-accent-color, var(--mj-brand-primary));\n}\n\n/* Sort icon animation */\n::ng-deep .ag-theme-alpine .ag-sort-indicator-icon {\n transition: transform 0.2s ease;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover .ag-sort-indicator-icon {\n transform: scale(1.1);\n}\n\n/* ========================================\n Enhanced Multi-Sort Indicators\n These styles use :host to ensure they're scoped to this component\n and !important to override AG Grid's built-in styles\n ======================================== */\n\n/* Highlight sorted column headers with eye-catching background */\n/* Ascending = blue tint */\n:host ::ng-deep .ag-header-cell-sorted-asc {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface)) !important;\n position: relative;\n}\n\n/* Descending = pink tint */\n:host ::ng-deep .ag-header-cell-sorted-desc {\n background: color-mix(in srgb, var(--mj-status-error) 12%, var(--mj-bg-surface)) !important;\n position: relative;\n}\n\n/* Bottom border accent for sorted columns - ascending = blue */\n:host ::ng-deep .ag-header-cell-sorted-asc::after {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: var(--mj-brand-primary);\n}\n\n/* Bottom border accent for sorted columns - descending = pink */\n:host ::ng-deep .ag-header-cell-sorted-desc::after {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: var(--mj-status-error);\n}\n\n/* Sorted column header text - ascending = blue, bold */\n:host ::ng-deep .ag-header-cell-sorted-asc .ag-header-cell-text {\n color: var(--mj-brand-primary) !important;\n font-weight: 700 !important;\n}\n\n/* Sorted column header text - descending = pink, bold */\n:host ::ng-deep .ag-header-cell-sorted-desc .ag-header-cell-text {\n color: var(--mj-status-error) !important;\n font-weight: 700 !important;\n}\n\n/* Sort icons - larger and more prominent */\n/* Ascending = blue */\n:host ::ng-deep .ag-sort-ascending-icon {\n color: var(--mj-brand-primary) !important;\n}\n\n/* Descending = pink/magenta */\n:host ::ng-deep .ag-sort-descending-icon {\n color: var(--mj-status-error) !important;\n}\n\n:host ::ng-deep .ag-sort-ascending-icon .ag-icon,\n:host ::ng-deep .ag-sort-descending-icon .ag-icon {\n font-size: 14px !important;\n}\n\n/* Sort order number (1, 2, 3) - show as plain number, no bubble */\n/* This avoids AG Grid bug where .ag-sort-order is always present but empty when unsorted */\n/* The number is only visible when AG Grid populates it (i.e., when column is sorted) */\n/* Default blue for ascending */\n:host ::ng-deep .ag-sort-order {\n margin-left: 4px;\n font-size: 11px !important;\n font-weight: 700 !important;\n color: var(--mj-brand-primary) !important;\n}\n\n/* Sort order number - pink for descending */\n:host ::ng-deep .ag-header-cell-sorted-desc .ag-sort-order {\n color: var(--mj-status-error) !important;\n}\n\n/* Hover state for sortable headers */\n:host ::ng-deep .ag-header-cell-sortable:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n:host ::ng-deep .ag-header-cell-sortable:hover .ag-header-cell-text {\n color: var(--mj-brand-primary);\n}\n\n/* ========================================\n Aggregate Summary Row\n ======================================== */\n\n.mj-aggregate-summary {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-default);\n font-size: 13px;\n min-height: 44px;\n}\n\n.aggregate-summary-content {\n display: flex;\n flex-wrap: wrap;\n gap: 20px;\n align-items: center;\n}\n\n.aggregate-summary-item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n}\n\n.aggregate-summary-item i {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.agg-summary-label {\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n.agg-summary-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n\n.aggregate-loading {\n display: flex;\n align-items: center;\n color: var(--mj-text-disabled);\n}\n\n.aggregate-loading i {\n font-size: 14px;\n}\n\n/* ========================================\n AGGREGATE CARDS (displayed above grid)\n ======================================== */\n\n.mj-aggregate-cards {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.aggregate-card {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);\n min-width: 140px;\n transition: all 0.15s ease;\n}\n\n.aggregate-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);\n}\n\n.aggregate-card-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-radius: 10px;\n color: var(--mj-brand-primary);\n font-size: 16px;\n flex-shrink: 0;\n}\n\n.aggregate-card-content {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.aggregate-card-label {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.aggregate-card-value {\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.aggregate-card-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 12px;\n color: var(--mj-text-disabled);\n}\n\n.aggregate-card-loading i {\n font-size: 16px;\n}\n\n/* ========================================\n Text Wrapping Styles\n ======================================== */\n\n/* Cell class for wrapped text */\n::ng-deep .cell-wrap-text {\n white-space: normal !important;\n word-wrap: break-word;\n line-height: 1.4;\n padding-top: 8px !important;\n padding-bottom: 8px !important;\n}\n\n/* Ensure AG Grid cells allow wrapping */\n::ng-deep .ag-cell.cell-wrap-text {\n overflow: visible;\n text-overflow: clip;\n}\n\n/* Maintain minimum row height when wrapping */\n::ng-deep .ag-row[style*=\"auto-height\"] {\n min-height: 40px;\n}\n\n/* ========================================\n Width filler column ([FillWidth])\n ========================================\n The trailing filler exists so row banding and horizontal rules reach the container's right edge\n instead of stopping wherever the last real column ends. For that to read as \"the table extends\"\n rather than \"there is an empty column here\", it must lose every affordance that makes a column\n look like a column: its own vertical rule, the header hover/menu, and the resize handle. What is\n left is bare row background \u2014 which is exactly the point. */\n::ng-deep .ag-header-cell.mj-grid-filler-header,\n::ng-deep .ag-cell.mj-grid-filler-cell {\n border-left: none !important;\n border-right: none !important;\n cursor: default;\n}\n\n/* The hover tint is OURS (see \".ag-theme-alpine .ag-header-cell:hover\" above), not AG Grid's, so it\n has to be switched off here or the filler lights up as though it were interactive. Everything else\n the filler needs suppressed \u2014 resize grip, header menu, moving, tool-panel entry \u2014 is handled by\n native colDef flags on the column itself, not by CSS. */\n::ng-deep .ag-header-cell.mj-grid-filler-header:hover {\n background-color: transparent !important;\n}\n\n/* AG Grid injects cellRenderer HTML without Angular attributes, so these\n must pierce encapsulation or the image renders at intrinsic SVG size. */\n::ng-deep .ag-cell.mj-grid-image-cell {\n display: flex !important;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 4px !important;\n}\n::ng-deep .ag-header-cell.mj-grid-image-header .ag-header-cell-label {\n justify-content: center;\n}\n::ng-deep .cell-image-link {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n overflow: hidden;\n border-radius: 50%;\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n::ng-deep .cell-image {\n width: 100%;\n height: 100%;\n max-width: 28px;\n max-height: 28px;\n object-fit: cover;\n object-position: center;\n display: block;\n}\n"] }]
|
|
5369
|
+
], template: "<!-- Grid Container -->\n<div\n #gridContainer\n [class]=\"gridContainerClasses.join(' ')\"\n [style.height]=\"gridHeightStyle\">\n\n <!-- Toolbar -->\n @if (ShowToolbar) {\n <div class=\"mj-grid-toolbar\">\n <div class=\"toolbar-left\">\n <!-- Search -->\n @if (ShowSearch) {\n <div class=\"toolbar-search\">\n <i class=\"fa-solid fa-search search-icon\"></i>\n <input\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"ToolbarConfig.searchPlaceholder || 'Search...'\"\n [value]=\"FilterText\"\n (input)=\"FilterText = $any($event.target).value\" />\n @if (FilterText) {\n <button\n class=\"search-clear\"\n (click)=\"FilterText = ''\"\n aria-label=\"Clear search\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n }\n </div>\n }\n <!-- Custom Left Buttons -->\n @for (button of ToolbarConfig.customButtons; track button) {\n @if (button.position !== 'right' && isButtonVisible(button)) {\n <button\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n @if (button.icon) {\n <i [class]=\"button.icon\"></i>\n }\n @if (button.text) {\n <span>{{ button.text }}</span>\n }\n </button>\n }\n }\n </div>\n <div class=\"toolbar-center\">\n <!-- Row Count -->\n @if (ToolbarConfig.showRowCount !== false) {\n <span class=\"row-count\">\n {{ totalRowCount }} {{ totalRowCount === 1 ? 'row' : 'rows' }}\n </span>\n }\n <!-- Selection Count -->\n @if (ToolbarConfig.showSelectionCount && SelectedKeys.length > 0) {\n <span class=\"selection-count\">\n ({{ SelectedKeys.length }} selected)\n </span>\n }\n </div>\n <div class=\"toolbar-right\">\n <!-- Recycle Bin chip \u2014 auto-hides when no deleted records, no tracking,\n or user lacks Delete permission. -->\n @if (ShowRecycleBin) {\n <mj-recycle-bin-chip [EntityName]=\"entityInfoName\"></mj-recycle-bin-chip>\n }\n <!-- New/Add Button (predefined) -->\n @if (ShowNewButton) {\n <button\n class=\"toolbar-button\"\n title=\"Create new record\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n <span class=\"button-text\">New</span>\n </button>\n }\n <!-- Refresh Button (predefined) -->\n @if (ShowRefreshButton) {\n <button\n class=\"toolbar-button\"\n title=\"Refresh data\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-arrows-rotate\" [class.fa-spin]=\"loading\"></i>\n <span class=\"button-text\">Refresh</span>\n </button>\n }\n <!-- Export Button (predefined) -->\n @if (ShowExportButton) {\n <button\n class=\"toolbar-button\"\n title=\"Export\"\n [disabled]=\"IsPreparingExport\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid\" [class.fa-file-excel]=\"!IsPreparingExport\" [class.fa-spinner]=\"IsPreparingExport\" [class.fa-spin]=\"IsPreparingExport\"></i>\n <span class=\"button-text\">Export</span>\n </button>\n }\n <!-- Delete Button (predefined) -->\n @if (ShowDeleteButton && HasSelection) {\n <button\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete selected records\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n <span class=\"button-text\">Delete</span>\n </button>\n }\n <!-- Compare Button (predefined) -->\n @if (ShowCompareButton) {\n <button\n class=\"toolbar-button\"\n title=\"Compare selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onCompareClick()\">\n <i class=\"fa-solid fa-code-compare\"></i>\n <span class=\"button-text\">Compare</span>\n </button>\n }\n <!-- Merge Button (predefined) -->\n @if (ShowMergeButton) {\n <button\n class=\"toolbar-button\"\n title=\"Merge selected records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onMergeClick()\">\n <i class=\"fa-solid fa-code-merge\"></i>\n <span class=\"button-text\">Merge</span>\n </button>\n }\n <!-- Add to List Button (predefined) -->\n @if (ShowAddToListButton) {\n <button\n class=\"toolbar-button\"\n [title]=\"HasSelection ? 'Add selected records to a list' : 'Add all matching records to a list'\"\n [disabled]=\"(!HasSelection && !ExportDataProvider) || IsPreparingAddToList\"\n (click)=\"onAddToListClick()\">\n <i class=\"fa-solid\" [class.fa-list-check]=\"!IsPreparingAddToList\" [class.fa-spinner]=\"IsPreparingAddToList\" [class.fa-spin]=\"IsPreparingAddToList\"></i>\n <span class=\"button-text\">Add to List</span>\n </button>\n }\n <!-- Search for Duplicates Button (predefined) -->\n @if (ShowDuplicateSearchButton) {\n <button\n class=\"toolbar-button\"\n title=\"Search for duplicate records\"\n [disabled]=\"!HasMultipleSelection\"\n (click)=\"onDuplicateSearchClick()\">\n <i class=\"fa-solid fa-magnifying-glass-plus\"></i>\n <span class=\"button-text\">Find Duplicates</span>\n </button>\n }\n <!-- Communication Button (predefined) -->\n @if (ShowCommunicationButton && EntitySupportsCommunication) {\n <button\n class=\"toolbar-button\"\n title=\"Send message to selected records\"\n [disabled]=\"!HasSelection\"\n (click)=\"onCommunicationClick()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span class=\"button-text\">Send Message</span>\n </button>\n }\n <!-- Legacy ToolbarConfig buttons -->\n <!-- Add Button (legacy) -->\n @if (ToolbarConfig.showAdd && AllowAdd && !ShowNewButton) {\n <button\n class=\"toolbar-button\"\n title=\"Add New\"\n (click)=\"onAddClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n </button>\n }\n <!-- Refresh Button (legacy) -->\n @if (ToolbarConfig.showRefresh !== false && !ShowRefreshButton) {\n <button\n class=\"toolbar-button\"\n title=\"Refresh\"\n [disabled]=\"loading\"\n (click)=\"onRefreshClick()\">\n <i class=\"fa-solid fa-refresh\" [class.fa-spin]=\"loading\"></i>\n </button>\n }\n <!-- Delete Button (legacy) -->\n @if (ToolbarConfig.showDelete && AllowDelete && HasSelection && !ShowDeleteButton) {\n <button\n class=\"toolbar-button toolbar-button-danger\"\n title=\"Delete Selected\"\n (click)=\"onDeleteClick()\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n }\n <!-- Export Button (legacy) -->\n @if (ToolbarConfig.showExport && !ShowExportButton) {\n <button\n class=\"toolbar-button\"\n title=\"Export\"\n (click)=\"onExportClick()\">\n <i class=\"fa-solid fa-download\"></i>\n </button>\n }\n <!-- Column Chooser Button -->\n @if (ToolbarConfig.showColumnChooser && AllowColumnToggle) {\n <button\n class=\"toolbar-button\"\n title=\"Column Chooser\"\n (click)=\"onColumnChooserClick()\">\n <i class=\"fa-solid fa-columns\"></i>\n </button>\n }\n <!-- Custom Right Buttons -->\n @for (button of ToolbarConfig.customButtons; track button) {\n @if (button.position === 'right' && isButtonVisible(button)) {\n <button\n class=\"toolbar-button\"\n [class]=\"button.cssClass\"\n [disabled]=\"isButtonDisabled(button)\"\n [title]=\"button.tooltip || ''\"\n (click)=\"onToolbarButtonClick(button)\">\n @if (button.icon) {\n <i [class]=\"button.icon\"></i>\n }\n @if (button.text) {\n <span>{{ button.text }}</span>\n }\n </button>\n }\n }\n <!-- Overflow Menu -->\n @if (hasOverflowMenuItems) {\n <div class=\"toolbar-overflow\" (click)=\"$event.stopPropagation()\">\n <button\n class=\"toolbar-button overflow-trigger\"\n title=\"More actions\"\n (click)=\"toggleOverflowMenu()\">\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </button>\n @if (showOverflowMenu) {\n <div class=\"overflow-menu\" [@fadeIn]>\n <!-- Export (if in overflow) -->\n @if (showExportInOverflow) {\n <button class=\"overflow-item\" (click)=\"onExportClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-file-excel\"></i>\n <span>Export</span>\n </button>\n }\n <!-- Entity Actions -->\n @if (ShowEntityActionButtons && EntityActions.length > 0) {\n <div class=\"overflow-divider\"></div>\n <div class=\"overflow-section-label\">Actions</div>\n @for (action of EntityActions; track action) {\n <button\n class=\"overflow-item\"\n [disabled]=\"!isEntityActionEnabled(action)\"\n (click)=\"onEntityActionClick(action); closeOverflowMenu()\">\n <i [class]=\"action.icon || 'fa-solid fa-bolt'\"></i>\n <span>{{ action.name }}</span>\n </button>\n }\n }\n <!-- Column Chooser (if in overflow) -->\n @if (showColumnChooserInOverflow) {\n <div class=\"overflow-divider\"></div>\n }\n @if (showColumnChooserInOverflow) {\n <button class=\"overflow-item\" (click)=\"onColumnChooserClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-columns\"></i>\n <span>Manage Columns</span>\n </button>\n }\n <!-- Selection-dependent actions in overflow -->\n @if (HasSelection && hasSelectionDependentOverflowActions) {\n <div class=\"overflow-divider\"></div>\n @if (showCommunicationInOverflow) {\n <button class=\"overflow-item\" (click)=\"onCommunicationClick(); closeOverflowMenu()\">\n <i class=\"fa-solid fa-envelope\"></i>\n <span>Send Message</span>\n </button>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Aggregate Cards (displayed as summary cards above the grid) -->\n @if (ShowAggregatePanel) {\n <div class=\"mj-aggregate-cards\">\n @for (agg of CardAggregates; track agg) {\n <div class=\"aggregate-card\">\n @if (agg.icon) {\n <div class=\"aggregate-card-icon\">\n <i [class]=\"agg.icon\"></i>\n </div>\n }\n <div class=\"aggregate-card-content\">\n <span class=\"aggregate-card-label\">{{ agg.label }}</span>\n <span class=\"aggregate-card-value\">{{ getAggregateValue(agg) }}</span>\n </div>\n </div>\n }\n @if (AggregatesLoading) {\n <div class=\"aggregate-card-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </div>\n }\n </div>\n }\n\n <!-- Grid Content -->\n <div class=\"mj-grid-content\">\n <!-- Loading Overlay -->\n @if (loading && rowData.length === 0) {\n <div class=\"mj-grid-loading-overlay\">\n <mj-loading text=\"Loading...\"></mj-loading>\n </div>\n }\n\n <!-- Error State -->\n @if (errorMessage && !loading) {\n <mj-empty-state\n class=\"mj-grid-state-fill\"\n [Size]=\"EmptyStateSize\"\n Variant=\"error\"\n Icon=\"fa-solid fa-triangle-exclamation\"\n Title=\"Couldn't load this data\"\n [Message]=\"FriendlyErrorMessage\"\n ActionText=\"Retry\"\n ActionIcon=\"fa-solid fa-arrows-rotate\"\n (Action)=\"Refresh()\" />\n }\n\n <!-- Empty State -->\n @if (!loading && !errorMessage && rowData.length === 0) {\n <mj-empty-state\n class=\"mj-grid-state-fill\"\n [Size]=\"EmptyStateSize\"\n Icon=\"fa-solid fa-inbox\"\n [Title]=\"EntityInfo ? 'No ' + EntityInfo.DisplayNamePlural + ' to display' : 'No data to display'\" />\n }\n\n <!-- AG Grid (Client-side mode) -->\n @if (!errorMessage && PaginationMode === 'client' && (rowData.length > 0 || loading)) {\n <ag-grid-angular\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [rowData]=\"rowData\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n (gridReady)=\"onGridReady($event)\"\n (cellClicked)=\"onAgCellClicked($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n }\n\n <!-- AG Grid (Infinite Scroll mode) -->\n @if (!errorMessage && PaginationMode === 'infinite') {\n <ag-grid-angular\n class=\"mj-ag-grid ag-theme-alpine\"\n [theme]=\"agGridTheme\"\n [columnDefs]=\"agColumnDefs\"\n [defaultColDef]=\"defaultColDef\"\n [rowSelection]=\"agRowSelection\"\n [getRowId]=\"getRowId\"\n [suppressCellFocus]=\"true\"\n [rowHeight]=\"RowHeight\"\n [headerHeight]=\"ShowHeader ? undefined : 0\"\n [rowModelType]=\"'infinite'\"\n [cacheBlockSize]=\"CacheBlockSize\"\n [maxBlocksInCache]=\"MaxBlocksInCache\"\n [infiniteInitialRowCount]=\"1\"\n [cacheOverflowSize]=\"2\"\n (gridReady)=\"onGridReady($event)\"\n (cellClicked)=\"onAgCellClicked($event)\"\n (rowClicked)=\"onAgRowClicked($event)\"\n (rowDoubleClicked)=\"onAgRowDoubleClicked($event)\"\n (sortChanged)=\"onAgSortChanged($event)\"\n (selectionChanged)=\"onAgSelectionChanged($event)\"\n (columnResized)=\"onAgColumnResized($event)\"\n (columnMoved)=\"onAgColumnMoved($event)\">\n </ag-grid-angular>\n }\n\n </div>\n\n <!-- Pagination (opt-in via ShowPager input) -->\n @if (ShowPager) {\n <mj-pagination\n [TotalRowCount]=\"totalRowCount\"\n [PageNumber]=\"PagerPageNumber\"\n [PageSize]=\"PageSize\"\n [IsLoading]=\"loading\"\n (PageChange)=\"PageChange.emit($event)\">\n </mj-pagination>\n }\n\n <!-- Aggregate Summary Row (outside mj-grid-content so it's always visible) -->\n @if (ShowAggregateSummary) {\n <div class=\"mj-aggregate-summary\">\n <div class=\"aggregate-summary-content\">\n @for (agg of ColumnAggregates; track agg) {\n <div class=\"aggregate-summary-item\">\n @if (agg.icon) {\n <i [class]=\"agg.icon\"></i>\n }\n <span class=\"agg-summary-label\">{{ agg.label }}:</span>\n <span class=\"agg-summary-value\">{{ getAggregateValue(agg) }}</span>\n </div>\n }\n </div>\n @if (AggregatesLoading) {\n <div class=\"aggregate-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </div>\n }\n </div>\n }\n</div>\n\n<!-- Export Dialog -->\n<mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n</mj-export-dialog>\n\n<!-- Runtime-UX driver for an entity action that names a RuntimeUXDriverClass (e.g. the Record Process\n bulk-update runner). Mounted in-place over the grid \u2014 no parent wiring required. -->\n@if (ActiveRuntimeDriver) {\n <mj-entity-action-ux-host\n [DriverClass]=\"ActiveRuntimeDriver.DriverClass\"\n [Context]=\"ActiveRuntimeDriver.Context\"\n (Completed)=\"OnRuntimeDriverCompleted($event)\"\n (Cancelled)=\"OnRuntimeDriverCancelled()\">\n </mj-entity-action-ux-host>\n}\n", styles: ["/* ========================================\n CSS Custom Properties (Theme Variables)\n ======================================== */\n\n:host {\n /* Grid container */\n --grid-border-color: var(--mj-border-default);\n --grid-border-radius: 0px;\n --grid-background: var(--mj-bg-surface);\n\n /* Header */\n --grid-header-bg: var(--mj-bg-surface-card);\n --grid-header-text: var(--mj-text-primary);\n --grid-header-font-weight: 600;\n --grid-header-height: 40px;\n --grid-header-border-color: var(--mj-border-default);\n\n /* Rows */\n --grid-row-height: 40px;\n --grid-row-bg: var(--mj-bg-surface);\n --grid-row-bg-alt: var(--mj-bg-surface-card);\n --grid-row-hover-bg: var(--mj-bg-surface-hover);\n --grid-row-selected-bg: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n --grid-row-selected-hover-bg: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n\n /* Cells */\n --grid-cell-padding: 8px 12px;\n --grid-cell-text: var(--mj-text-primary);\n --grid-cell-border-color: var(--mj-border-subtle);\n\n /* Selection - mellow yellow to avoid conflict with blue hyperlinks */\n --grid-checkbox-color: var(--mj-brand-primary);\n --grid-selection-indicator-color: var(--mj-status-warning);\n\n /* Editing */\n --grid-edit-cell-bg: var(--mj-bg-surface);\n --grid-edit-cell-border: var(--mj-brand-primary);\n --grid-edit-cell-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n\n /* Sorting */\n --grid-sort-indicator-color: var(--mj-brand-primary);\n\n /* Toolbar */\n --grid-toolbar-bg: var(--mj-bg-surface);\n --grid-toolbar-height: 48px;\n --grid-toolbar-border-color: var(--mj-border-default);\n\n /* Loading */\n --grid-loading-overlay-bg: color-mix(in srgb, var(--mj-bg-surface) 80%, transparent);\n\n /* Empty state */\n --grid-empty-text-color: var(--mj-text-disabled);\n --grid-empty-icon-color: var(--mj-border-strong);\n\n display: block;\n height: 100%;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\n}\n\n/* ========================================\n Grid Container\n ======================================== */\n\n.mj-grid-container {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--grid-border-color);\n border-radius: var(--grid-border-radius);\n background: var(--grid-background);\n overflow: hidden;\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.mj-grid-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: var(--grid-toolbar-height);\n padding: 0 12px;\n background: var(--grid-toolbar-bg);\n border-bottom: 1px solid var(--grid-toolbar-border-color);\n gap: 12px;\n}\n\n.toolbar-left,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-center {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--mj-text-secondary);\n font-size: 13px;\n}\n\n.toolbar-search {\n display: flex;\n align-items: center;\n position: relative;\n}\n\n.search-icon {\n position: absolute;\n left: 10px;\n color: var(--mj-text-disabled);\n font-size: 13px;\n}\n\n.search-input {\n padding: 6px 30px 6px 32px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n font-size: 13px;\n width: 200px;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n.search-input:focus {\n outline: none;\n border-color: var(--grid-selection-indicator-color);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.search-clear {\n position: absolute;\n right: 6px;\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--mj-text-disabled);\n font-size: 12px;\n}\n\n.search-clear:hover {\n color: var(--mj-text-secondary);\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n color: var(--mj-text-primary);\n transition: background-color 0.2s, border-color 0.2s;\n}\n\n.toolbar-button:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.toolbar-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n.toolbar-button-danger {\n color: var(--mj-status-error);\n}\n\n.toolbar-button-danger:hover:not(:disabled) {\n background: var(--mj-status-error-bg);\n border-color: var(--mj-status-error);\n}\n\n.row-count,\n.selection-count {\n font-weight: 500;\n}\n\n/* ========================================\n Grid Content\n ======================================== */\n\n.mj-grid-content {\n flex: 1;\n position: relative;\n overflow: hidden;\n}\n\n.mj-grid-scroll-container {\n height: 100%;\n overflow: auto;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.mj-grid-loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--grid-loading-overlay-bg);\n z-index: 10;\n}\n\n/* ========================================\n Error State\n ======================================== */\n\n/* ========================================\n Empty / Error State\n ======================================== */\n\n/* Flex-fill so <mj-empty-state> placeholders occupy the grid content area\n (the host centers its content but does not grow to fill the parent). */\n.mj-grid-state-fill {\n flex: 1 1 auto;\n}\n\n/* ========================================\n Header Row\n ======================================== */\n\n.mj-grid-header {\n display: flex;\n min-height: var(--grid-header-height);\n background: var(--grid-header-bg);\n border-bottom: 2px solid var(--grid-header-border-color);\n position: sticky;\n top: 0;\n z-index: 5;\n}\n\n.mj-grid-header-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n font-weight: var(--grid-header-font-weight);\n color: var(--grid-header-text);\n font-size: 13px;\n user-select: none;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-right: 1px solid var(--grid-cell-border-color);\n flex-shrink: 0;\n}\n\n.mj-grid-header-cell:last-child {\n border-right: none;\n}\n\n.mj-grid-header-cell.sortable {\n cursor: pointer;\n}\n\n.mj-grid-header-cell.sortable:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n.header-text {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sort-indicator {\n display: flex;\n align-items: center;\n margin-left: 6px;\n color: var(--grid-sort-indicator-color);\n}\n\n.sort-indicator i {\n font-size: 12px;\n}\n\n.sort-index {\n font-size: 10px;\n margin-left: 2px;\n font-weight: normal;\n}\n\n/* ========================================\n Data Rows\n ======================================== */\n\n.mj-grid-row {\n display: flex;\n align-items: stretch;\n background: var(--grid-row-bg);\n transition: background-color 0.15s;\n cursor: default;\n}\n\n.mj-grid-row:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-alt {\n background: var(--grid-row-bg-alt);\n}\n\n.mj-grid-row.grid-row-alt:hover {\n background: var(--grid-row-hover-bg);\n}\n\n.mj-grid-row.grid-row-selected {\n background: var(--grid-row-selected-bg);\n}\n\n.mj-grid-row.grid-row-selected:hover {\n background: var(--grid-row-selected-hover-bg);\n}\n\n.mj-grid-row.grid-row-editing {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, var(--mj-bg-surface));\n}\n\n.mj-grid-row.grid-row-dirty {\n border-left: 3px solid var(--mj-status-warning);\n}\n\n/* ========================================\n Data Cells\n ======================================== */\n\n.mj-grid-cell {\n display: flex;\n align-items: center;\n padding: var(--grid-cell-padding);\n color: var(--grid-cell-text);\n font-size: 13px;\n overflow: hidden;\n border-right: 1px solid transparent;\n flex-shrink: 0;\n}\n\n.mj-grid-cell:last-child {\n border-right: none;\n}\n\n/* Grid lines modes */\n.grid-lines-horizontal .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-vertical .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-row {\n border-bottom: 1px solid var(--grid-cell-border-color);\n}\n\n.grid-lines-both .mj-grid-cell {\n border-right: 1px solid var(--grid-cell-border-color);\n}\n\n/* Cell alignment */\n.mj-grid-cell.align-left {\n justify-content: flex-start;\n}\n\n.mj-grid-cell.align-center {\n justify-content: center;\n}\n\n.mj-grid-cell.align-right {\n justify-content: flex-end;\n}\n\n.cell-content {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Special cells */\n.row-number-cell {\n width: 50px;\n min-width: 50px;\n max-width: 50px;\n justify-content: center;\n color: var(--mj-text-disabled);\n font-size: 12px;\n background: var(--grid-header-bg);\n}\n\n.checkbox-cell {\n width: 40px;\n min-width: 40px;\n max-width: 40px;\n justify-content: center;\n}\n\n.checkbox-cell input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--grid-checkbox-color);\n}\n\n/* ========================================\n Virtual Scrolling\n ======================================== */\n\n.mj-grid-virtual-spacer {\n flex-shrink: 0;\n}\n\n/* ========================================\n Responsive Adjustments\n ======================================== */\n\n@media (max-width: 768px) {\n .mj-grid-toolbar {\n flex-wrap: wrap;\n padding: 8px;\n }\n\n .toolbar-search {\n order: 3;\n width: 100%;\n margin-top: 8px;\n }\n\n .search-input {\n width: 100%;\n }\n\n .toolbar-center {\n order: 2;\n }\n\n /* Hide button text on mobile */\n .toolbar-button .button-text {\n display: none;\n }\n}\n\n/* ========================================\n Toolbar Button Text\n ======================================== */\n\n.toolbar-button .button-text {\n font-size: 13px;\n}\n\n/* ========================================\n Overflow Menu\n ======================================== */\n\n.toolbar-overflow {\n position: relative;\n}\n\n.overflow-trigger {\n padding: 6px 8px !important;\n}\n\n.overflow-trigger i {\n font-size: 16px;\n}\n\n.overflow-menu {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 4px;\n min-width: 220px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.08);\n z-index: 1000;\n overflow: hidden;\n}\n\n.overflow-item {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 10px 16px;\n border: none;\n background: none;\n cursor: pointer;\n font-size: 14px;\n color: var(--mj-text-primary);\n text-align: left;\n gap: 12px;\n transition: background-color 0.15s;\n}\n\n.overflow-item:hover:not(:disabled) {\n background: var(--mj-bg-surface-card);\n}\n\n.overflow-item:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.overflow-item i {\n width: 18px;\n font-size: 14px;\n color: var(--mj-text-secondary);\n text-align: center;\n}\n\n.overflow-item span {\n flex: 1;\n}\n\n.overflow-divider {\n height: 1px;\n background: var(--mj-border-default);\n margin: 4px 0;\n}\n\n.overflow-section-label {\n padding: 8px 16px 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--mj-text-disabled);\n letter-spacing: 0.5px;\n}\n\n/* Entity Actions submenu styling */\n.overflow-item.action-item i {\n color: var(--mj-brand-primary);\n}\n\n/* ========================================\n Highlight Matches\n ======================================== */\n\n::ng-deep .highlight-match {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-radius: 2px;\n padding: 0 1px;\n}\n\n/* ========================================\n AG Grid Customizations\n ======================================== */\n\n.mj-ag-grid {\n width: 100%;\n height: 100%;\n}\n\n/* AG Grid v35 uses JS-based theming (colorSchemeVariable) \u2014 no CSS variable overrides needed */\n\n/* Selected row styling - left indicator bar only, background handled by AG Grid theme */\n::ng-deep .ag-row-selected {\n box-shadow: inset 4px 0 0 0 var(--mj-status-warning);\n}\n\n/* Selection checkbox styling */\n::ng-deep .ag-checkbox-input-wrapper {\n width: 18px;\n height: 18px;\n}\n\n::ng-deep .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--grid-checkbox-color, var(--mj-brand-primary));\n}\n\n/* ========================================\n Visual Config: Header Styles\n ======================================== */\n\n/* Flat header (minimal) */\n.header-style-flat ::ng-deep .ag-header {\n background: var(--grid-header-bg, var(--mj-bg-surface-card));\n border-bottom: 1px solid var(--grid-header-border-color, var(--mj-border-default));\n}\n\n/* Elevated header (default - subtle shadow) */\n.header-style-elevated ::ng-deep .ag-header {\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--grid-header-border-color, var(--mj-border-default));\n}\n\n.header-style-elevated.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);\n}\n\n/* Gradient header (more prominent) */\n.header-style-gradient ::ng-deep .ag-header {\n background: var(--mj-bg-surface-sunken);\n border-bottom: none;\n}\n\n.header-style-gradient.header-shadow ::ng-deep .ag-header {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n/* Bold header (high contrast) */\n.header-style-bold ::ng-deep .ag-header {\n background: var(--mj-text-secondary);\n border-bottom: none;\n}\n\n.header-style-bold ::ng-deep .ag-header-cell-text {\n color: var(--mj-text-inverse);\n font-weight: 600;\n}\n\n.header-style-bold ::ng-deep .ag-header-icon {\n color: color-mix(in srgb, var(--mj-text-inverse) 70%, transparent);\n}\n\n.header-style-bold ::ng-deep .ag-header-cell:hover {\n background: color-mix(in srgb, var(--mj-text-inverse) 10%, transparent);\n}\n\n/* Header sort icons enhancement */\n::ng-deep .ag-header-cell-sorted-asc .ag-icon-asc,\n::ng-deep .ag-header-cell-sorted-desc .ag-icon-desc {\n color: var(--grid-accent-color, var(--grid-sort-indicator-color, var(--mj-brand-primary)));\n}\n\n/* ========================================\n Visual Config: Zebra Striping\n ======================================== */\n\n/* Subtle contrast */\n.alternate-rows-subtle ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.015);\n}\n\n.alternate-rows-subtle ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, var(--mj-bg-surface-hover));\n}\n\n/* Medium contrast (default) */\n.alternate-rows-medium ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.025);\n}\n\n.alternate-rows-medium ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, var(--mj-bg-surface-hover));\n}\n\n/* Strong contrast */\n.alternate-rows-strong ::ng-deep .ag-row-odd {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.alternate-rows-strong ::ng-deep .ag-row-odd:hover:not(.ag-row-selected) {\n background-color: var(--grid-row-hover-bg, var(--mj-bg-surface-hover));\n}\n\n/* ========================================\n Visual Config: Hover Transitions\n ======================================== */\n\n.hover-transitions ::ng-deep .ag-row {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n.hover-transitions ::ng-deep .ag-cell {\n transition: background-color var(--grid-hover-transition, 150ms) ease;\n}\n\n/* ========================================\n Visual Config: Cell Padding\n ======================================== */\n\n.cell-padding-compact ::ng-deep .ag-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-compact ::ng-deep .ag-header-cell {\n padding-left: 8px;\n padding-right: 8px;\n}\n\n.cell-padding-normal ::ng-deep .ag-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-normal ::ng-deep .ag-header-cell {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n.cell-padding-comfortable ::ng-deep .ag-header-cell {\n padding-left: 16px;\n padding-right: 16px;\n}\n\n/* ========================================\n Visual Config: Checkbox Styles\n ======================================== */\n\n/* Rounded checkbox */\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper {\n border-radius: 4px;\n}\n\n.checkbox-style-rounded ::ng-deep .ag-checkbox-input-wrapper::after {\n border-radius: 3px;\n}\n\n/* Filled checkbox */\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked {\n background-color: var(--grid-checkbox-color, var(--mj-brand-primary));\n border-color: var(--grid-checkbox-color, var(--mj-brand-primary));\n}\n\n.checkbox-style-filled ::ng-deep .ag-checkbox-input-wrapper.ag-checked::after {\n color: var(--mj-text-inverse);\n}\n\n/* ========================================\n Cell Content Formatting\n ======================================== */\n\n/* Right-aligned cells (numbers) */\n::ng-deep .cell-align-right {\n text-align: right;\n justify-content: flex-end;\n}\n\n::ng-deep .header-align-right .ag-header-cell-label {\n justify-content: flex-end;\n}\n\n/* Empty cell placeholder */\n::ng-deep .cell-empty {\n color: var(--mj-text-disabled);\n font-style: normal;\n}\n\n/* Boolean icons */\n::ng-deep .cell-boolean-true {\n color: var(--mj-status-success);\n font-size: 14px;\n}\n\n::ng-deep .cell-boolean-false {\n color: var(--mj-text-disabled);\n font-size: 14px;\n}\n\n/* Clickable links */\n::ng-deep .cell-link {\n color: var(--grid-accent-color, var(--mj-brand-primary));\n text-decoration: none;\n transition: color 0.15s;\n font-size: inherit;\n font-family: inherit;\n line-height: inherit;\n}\n\n::ng-deep .cell-link:hover {\n color: var(--mj-brand-primary);\n text-decoration: underline;\n}\n\n/* Email cells */\n::ng-deep .cell-email {\n font-family: inherit;\n font-size: 13px;\n}\n\n/* Foreign key link cells */\n::ng-deep .cell-fk-link {\n color: var(--grid-accent-color, var(--mj-brand-primary));\n text-decoration: none;\n cursor: pointer;\n transition: color 0.15s, text-decoration 0.15s;\n}\n\n::ng-deep .cell-fk-link:hover {\n color: var(--mj-brand-primary-hover);\n text-decoration: underline;\n}\n\n::ng-deep .cell-fk-link:active {\n color: var(--mj-brand-primary-active);\n}\n\n/* URL cells */\n::ng-deep .cell-url {\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n font-size: 13px;\n}\n\n/* Phone cells */\n::ng-deep .cell-phone {\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n}\n\n/* ========================================\n Skeleton Loading Animation\n ======================================== */\n\n@keyframes skeleton-shimmer {\n 0% {\n background-position: -200px 0;\n }\n 100% {\n background-position: calc(200px + 100%) 0;\n }\n}\n\n.skeleton-row {\n display: flex;\n height: 40px;\n align-items: center;\n padding: 0 12px;\n border-bottom: 1px solid var(--grid-cell-border-color, var(--mj-border-subtle));\n}\n\n.skeleton-cell {\n height: 16px;\n border-radius: 4px;\n background: linear-gradient(\n 90deg,\n var(--mj-bg-surface-sunken) 0px,\n var(--mj-bg-surface-hover) 40px,\n var(--mj-bg-surface-sunken) 80px\n );\n background-size: 200px 100%;\n animation: skeleton-shimmer 1.5s ease-in-out infinite;\n}\n\n.skeleton-cell-short {\n width: 60px;\n}\n\n.skeleton-cell-medium {\n width: 120px;\n}\n\n.skeleton-cell-long {\n width: 180px;\n}\n\n/* Selection checkbox column header */\n::ng-deep .ag-header-select-all {\n margin-right: 0;\n}\n\n/* ========================================\n Row Border Enhancement\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-row {\n border-bottom: 1px solid var(--grid-cell-border-color, var(--mj-border-subtle));\n}\n\n::ng-deep .ag-theme-alpine .ag-row:last-child {\n border-bottom: none;\n}\n\n/* ========================================\n Focus States\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-cell-focus {\n border: none !important;\n outline: none !important;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:focus {\n outline: 2px solid var(--grid-accent-color, var(--mj-brand-primary));\n outline-offset: -2px;\n}\n\n/* ========================================\n Scrollbar Styling\n ======================================== */\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-track {\n background: var(--mj-bg-surface-card);\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 4px;\n}\n\n::ng-deep .ag-body-viewport::-webkit-scrollbar-thumb:hover {\n background: var(--mj-text-disabled);\n}\n\n/* ========================================\n Pinned Column Styling\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-pinned-left-cols-container {\n border-right: 2px solid var(--grid-border-color, var(--mj-border-default));\n}\n\n::ng-deep .ag-theme-alpine .ag-pinned-right-cols-container {\n border-left: 2px solid var(--grid-border-color, var(--mj-border-default));\n}\n\n/* ========================================\n Header Cell Enhancements\n ======================================== */\n\n::ng-deep .ag-theme-alpine .ag-header-cell {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n color: var(--mj-text-secondary);\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell-sortable:hover .ag-header-cell-label {\n color: var(--grid-accent-color, var(--mj-brand-primary));\n}\n\n/* Sort icon animation */\n::ng-deep .ag-theme-alpine .ag-sort-indicator-icon {\n transition: transform 0.2s ease;\n}\n\n::ng-deep .ag-theme-alpine .ag-header-cell:hover .ag-sort-indicator-icon {\n transform: scale(1.1);\n}\n\n/* ========================================\n Enhanced Multi-Sort Indicators\n These styles use :host to ensure they're scoped to this component\n and !important to override AG Grid's built-in styles\n ======================================== */\n\n/* Highlight sorted column headers with eye-catching background */\n/* Ascending = blue tint */\n:host ::ng-deep .ag-header-cell-sorted-asc {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface)) !important;\n position: relative;\n}\n\n/* Descending = pink tint */\n:host ::ng-deep .ag-header-cell-sorted-desc {\n background: color-mix(in srgb, var(--mj-status-error) 12%, var(--mj-bg-surface)) !important;\n position: relative;\n}\n\n/* Bottom border accent for sorted columns - ascending = blue */\n:host ::ng-deep .ag-header-cell-sorted-asc::after {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: var(--mj-brand-primary);\n}\n\n/* Bottom border accent for sorted columns - descending = pink */\n:host ::ng-deep .ag-header-cell-sorted-desc::after {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 3px;\n background: var(--mj-status-error);\n}\n\n/* Sorted column header text - ascending = blue, bold */\n:host ::ng-deep .ag-header-cell-sorted-asc .ag-header-cell-text {\n color: var(--mj-brand-primary) !important;\n font-weight: 700 !important;\n}\n\n/* Sorted column header text - descending = pink, bold */\n:host ::ng-deep .ag-header-cell-sorted-desc .ag-header-cell-text {\n color: var(--mj-status-error) !important;\n font-weight: 700 !important;\n}\n\n/* Sort icons - larger and more prominent */\n/* Ascending = blue */\n:host ::ng-deep .ag-sort-ascending-icon {\n color: var(--mj-brand-primary) !important;\n}\n\n/* Descending = pink/magenta */\n:host ::ng-deep .ag-sort-descending-icon {\n color: var(--mj-status-error) !important;\n}\n\n:host ::ng-deep .ag-sort-ascending-icon .ag-icon,\n:host ::ng-deep .ag-sort-descending-icon .ag-icon {\n font-size: 14px !important;\n}\n\n/* Sort order number (1, 2, 3) - show as plain number, no bubble */\n/* This avoids AG Grid bug where .ag-sort-order is always present but empty when unsorted */\n/* The number is only visible when AG Grid populates it (i.e., when column is sorted) */\n/* Default blue for ascending */\n:host ::ng-deep .ag-sort-order {\n margin-left: 4px;\n font-size: 11px !important;\n font-weight: 700 !important;\n color: var(--mj-brand-primary) !important;\n}\n\n/* Sort order number - pink for descending */\n:host ::ng-deep .ag-header-cell-sorted-desc .ag-sort-order {\n color: var(--mj-status-error) !important;\n}\n\n/* Hover state for sortable headers */\n:host ::ng-deep .ag-header-cell-sortable:hover {\n background: rgba(0, 0, 0, 0.04);\n}\n\n:host ::ng-deep .ag-header-cell-sortable:hover .ag-header-cell-text {\n color: var(--mj-brand-primary);\n}\n\n/* ========================================\n Aggregate Summary Row\n ======================================== */\n\n.mj-aggregate-summary {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-default);\n font-size: 13px;\n min-height: 44px;\n}\n\n.aggregate-summary-content {\n display: flex;\n flex-wrap: wrap;\n gap: 20px;\n align-items: center;\n}\n\n.aggregate-summary-item {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);\n}\n\n.aggregate-summary-item i {\n font-size: 12px;\n color: var(--mj-text-muted);\n}\n\n.agg-summary-label {\n color: var(--mj-text-muted);\n font-weight: 500;\n}\n\n.agg-summary-value {\n color: var(--mj-text-primary);\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n}\n\n.aggregate-loading {\n display: flex;\n align-items: center;\n color: var(--mj-text-disabled);\n}\n\n.aggregate-loading i {\n font-size: 14px;\n}\n\n/* ========================================\n AGGREGATE CARDS (displayed above grid)\n ======================================== */\n\n.mj-aggregate-cards {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.aggregate-card {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);\n min-width: 140px;\n transition: all 0.15s ease;\n}\n\n.aggregate-card:hover {\n border-color: var(--mj-border-strong);\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);\n}\n\n.aggregate-card-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n border-radius: 10px;\n color: var(--mj-brand-primary);\n font-size: 16px;\n flex-shrink: 0;\n}\n\n.aggregate-card-content {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.aggregate-card-label {\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.aggregate-card-value {\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.aggregate-card-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 12px;\n color: var(--mj-text-disabled);\n}\n\n.aggregate-card-loading i {\n font-size: 16px;\n}\n\n/* ========================================\n Text Wrapping Styles\n ======================================== */\n\n/* Cell class for wrapped text */\n::ng-deep .cell-wrap-text {\n white-space: normal !important;\n word-wrap: break-word;\n line-height: 1.4;\n padding-top: 8px !important;\n padding-bottom: 8px !important;\n}\n\n/* Ensure AG Grid cells allow wrapping */\n::ng-deep .ag-cell.cell-wrap-text {\n overflow: visible;\n text-overflow: clip;\n}\n\n/* Maintain minimum row height when wrapping */\n::ng-deep .ag-row[style*=\"auto-height\"] {\n min-height: 40px;\n}\n\n/* ========================================\n Width filler column ([FillWidth])\n ========================================\n The trailing filler exists so row banding and horizontal rules reach the container's right edge\n instead of stopping wherever the last real column ends. For that to read as \"the table extends\"\n rather than \"there is an empty column here\", it must lose every affordance that makes a column\n look like a column: its own vertical rule, the header hover/menu, and the resize handle. What is\n left is bare row background \u2014 which is exactly the point. */\n::ng-deep .ag-header-cell.mj-grid-filler-header,\n::ng-deep .ag-cell.mj-grid-filler-cell {\n border-left: none !important;\n border-right: none !important;\n cursor: default;\n}\n\n/* The hover tint is OURS (see \".ag-theme-alpine .ag-header-cell:hover\" above), not AG Grid's, so it\n has to be switched off here or the filler lights up as though it were interactive. Everything else\n the filler needs suppressed \u2014 resize grip, header menu, moving, tool-panel entry \u2014 is handled by\n native colDef flags on the column itself, not by CSS. */\n::ng-deep .ag-header-cell.mj-grid-filler-header:hover {\n background-color: transparent !important;\n}\n\n/* AG Grid injects cellRenderer HTML without Angular attributes, so these\n must pierce encapsulation or the image renders at intrinsic SVG size. */\n::ng-deep .ag-cell.mj-grid-image-cell {\n display: flex !important;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 4px !important;\n}\n::ng-deep .ag-header-cell.mj-grid-image-header .ag-header-cell-label {\n justify-content: center;\n}\n::ng-deep .cell-image-link {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n overflow: hidden;\n border-radius: 50%;\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n::ng-deep .cell-image {\n width: 100%;\n height: 100%;\n max-width: 28px;\n max-height: 28px;\n object-fit: cover;\n object-position: center;\n display: block;\n}\n"] }]
|
|
5076
5370
|
}], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i1.ExportService }, { type: i0.NgZone }], { Params: [{
|
|
5077
5371
|
type: Input
|
|
5078
5372
|
}], AllowLoad: [{
|
|
@@ -5315,9 +5609,9 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
5315
5609
|
type: Output
|
|
5316
5610
|
}], EntityActionRequested: [{
|
|
5317
5611
|
type: Output
|
|
5318
|
-
}],
|
|
5612
|
+
}], GridContainer: [{
|
|
5319
5613
|
type: ViewChild,
|
|
5320
5614
|
args: ['gridContainer']
|
|
5321
5615
|
}] }); })();
|
|
5322
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber:
|
|
5616
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber: 194 }); })();
|
|
5323
5617
|
//# sourceMappingURL=entity-data-grid.component.js.map
|