@memberjunction/ng-entity-viewer 6.2.0-edge.0 → 6.2.0-edge.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts +22 -0
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts.map +1 -1
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +57 -13
- package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.d.ts +67 -5
- package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.js +202 -71
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +159 -20
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.js +446 -167
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
- package/dist/lib/entity-data-grid/events/grid-events.js +67 -67
- package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +112 -8
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +245 -61
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +13 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +53 -10
- 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/export-column.util.d.ts +14 -0
- package/dist/lib/utils/export-column.util.d.ts.map +1 -0
- package/dist/lib/utils/export-column.util.js +34 -0
- package/dist/lib/utils/export-column.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 +17 -1
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
- package/dist/lib/view-config-panel/view-config-panel.component.js +39 -6
- package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
- package/dist/lib/view-selector/view-selector.component.js +2 -2
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts +15 -3
- package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js +33 -5
- package/dist/lib/view-types/renderers/cards-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +70 -2
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js +155 -33
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts +16 -2
- package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/map-view-renderer.component.js +37 -9
- package/dist/lib/view-types/renderers/map-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts +2 -0
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts.map +1 -1
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.js +11 -2
- package/dist/lib/view-types/renderers/timeline-view-renderer.component.js.map +1 -1
- package/dist/lib/view-types/view-type.contracts.d.ts +8 -0
- package/dist/lib/view-types/view-type.contracts.d.ts.map +1 -1
- package/dist/lib/view-types/view-type.contracts.js.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.d.ts +244 -25
- package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
- package/dist/lib/view-workspace/view-workspace.component.js +639 -182
- package/dist/lib/view-workspace/view-workspace.component.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,10 +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 { ExportColumnTypeForSQLType } from '../utils/export-column.util';
|
|
6
7
|
import { LogError, RunView, RunViewParams, CoerceImageSrc, ParseCssHexColor, CompositeKey, IsDateOnlySQLType, FormatDateOnly, EntityFieldTSType } from '@memberjunction/core';
|
|
7
8
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
8
9
|
import { EntityActionEngineBase } from '@memberjunction/actions-base';
|
|
9
|
-
import {
|
|
10
|
+
import { CommunicationEngineBase } from '@memberjunction/communication-types';
|
|
11
|
+
import { BuildPkString, CanonicalizeColumnFields, ComputeFieldsList } from '../utils/record.util';
|
|
10
12
|
import { AggregateField } from '../utils/aggregate-field.util';
|
|
11
13
|
import { ViewInfo, UserViewEngine, UserInfoEngine } from '@memberjunction/core-entities';
|
|
12
14
|
import { ModuleRegistry, AllCommunityModule, themeAlpine } from 'ag-grid-community';
|
|
@@ -456,7 +458,7 @@ function EntityDataGridComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
|
456
458
|
i0.ɵɵadvance();
|
|
457
459
|
i0.ɵɵconditional(ctx_r1.ShowDuplicateSearchButton ? 17 : -1);
|
|
458
460
|
i0.ɵɵadvance();
|
|
459
|
-
i0.ɵɵconditional(ctx_r1.ShowCommunicationButton ? 18 : -1);
|
|
461
|
+
i0.ɵɵconditional(ctx_r1.ShowCommunicationButton && ctx_r1.EntitySupportsCommunication ? 18 : -1);
|
|
460
462
|
i0.ɵɵadvance();
|
|
461
463
|
i0.ɵɵconditional(ctx_r1.ToolbarConfig.showAdd && ctx_r1.AllowAdd && !ctx_r1.ShowNewButton ? 19 : -1);
|
|
462
464
|
i0.ɵɵadvance();
|
|
@@ -535,7 +537,7 @@ function EntityDataGridComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
|
535
537
|
i0.ɵɵelement(0, "mj-empty-state", 6);
|
|
536
538
|
} if (rf & 2) {
|
|
537
539
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
538
|
-
i0.ɵɵproperty("Size", ctx_r1.EmptyStateSize);
|
|
540
|
+
i0.ɵɵproperty("Size", ctx_r1.EmptyStateSize)("Title", ctx_r1.EntityInfo ? "No " + ctx_r1.EntityInfo.DisplayNamePlural + " to display" : "No data to display");
|
|
539
541
|
} }
|
|
540
542
|
function EntityDataGridComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
541
543
|
const _r30 = i0.ɵɵgetCurrentView();
|
|
@@ -654,6 +656,8 @@ ModuleRegistry.registerModules([AllCommunityModule]);
|
|
|
654
656
|
const AUTO_WIDTH_COLUMN_MIN_PX = 160;
|
|
655
657
|
/** Column id of the inert width-filler appended when `FillWidth` is on. See `buildFillerColumnDef`. */
|
|
656
658
|
const FILLER_COLUMN_ID = '__mjFill';
|
|
659
|
+
/** Field (and column id) of the row-number column added when `ShowRowNumbers` is on. */
|
|
660
|
+
const ROW_NUMBER_FIELD = '__rowNumber';
|
|
657
661
|
/**
|
|
658
662
|
* Flex weight given to a `width: 'auto'` column so it outranks the filler when leftover row width is
|
|
659
663
|
* shared out. Any value that dwarfs the filler's weight of 1 works; the effect is "this column takes
|
|
@@ -793,9 +797,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
793
797
|
* Convenience accessor for the resolved entity name. Returns null when
|
|
794
798
|
* the entity hasn't been resolved yet (e.g., before data loads).
|
|
795
799
|
*/
|
|
796
|
-
get
|
|
800
|
+
get EntityInfoName() {
|
|
797
801
|
return this._entityInfo?.Name ?? null;
|
|
798
802
|
}
|
|
803
|
+
/** @deprecated Use {@link EntityInfoName}. */
|
|
804
|
+
get entityInfoName() {
|
|
805
|
+
return this.EntityInfoName;
|
|
806
|
+
}
|
|
799
807
|
/**
|
|
800
808
|
* Current page number for the shared pager (1-based).
|
|
801
809
|
* Set by parent when using external data with server-side paging.
|
|
@@ -1148,9 +1156,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1148
1156
|
/**
|
|
1149
1157
|
* Get the effective visual config (user config merged with defaults)
|
|
1150
1158
|
*/
|
|
1151
|
-
get
|
|
1159
|
+
get EffectiveVisualConfig() {
|
|
1152
1160
|
return { ...DEFAULT_VISUAL_CONFIG, ...this._visualConfig };
|
|
1153
1161
|
}
|
|
1162
|
+
/** @deprecated Use {@link EffectiveVisualConfig}. */
|
|
1163
|
+
get effectiveVisualConfig() {
|
|
1164
|
+
return this.EffectiveVisualConfig;
|
|
1165
|
+
}
|
|
1154
1166
|
// ========================================
|
|
1155
1167
|
// Toolbar Inputs
|
|
1156
1168
|
// ========================================
|
|
@@ -1325,6 +1337,45 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1325
1337
|
get ShowCommunicationButton() {
|
|
1326
1338
|
return this._showCommunicationButton;
|
|
1327
1339
|
}
|
|
1340
|
+
_entitySupportsCommunication = false;
|
|
1341
|
+
_communicationCheckedForEntityID = null;
|
|
1342
|
+
/**
|
|
1343
|
+
* True when the current entity has at least one active Entity Communication Message Type. "Send
|
|
1344
|
+
* Message" is offered — as a toolbar button or in the overflow menu — only when this is true, so an
|
|
1345
|
+
* entity with no communication setup never shows a control that cannot do anything.
|
|
1346
|
+
*/
|
|
1347
|
+
get EntitySupportsCommunication() {
|
|
1348
|
+
return this._entitySupportsCommunication;
|
|
1349
|
+
}
|
|
1350
|
+
/** Resolves {@link EntitySupportsCommunication} for the current entity, once per entity. */
|
|
1351
|
+
async resolveCommunicationSupport() {
|
|
1352
|
+
const entity = this._entityInfo;
|
|
1353
|
+
if (!entity) {
|
|
1354
|
+
this._entitySupportsCommunication = false;
|
|
1355
|
+
this._communicationCheckedForEntityID = null;
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
if (this._communicationCheckedForEntityID && UUIDsEqual(this._communicationCheckedForEntityID, entity.ID)) {
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
this._communicationCheckedForEntityID = entity.ID;
|
|
1362
|
+
this._entitySupportsCommunication = false;
|
|
1363
|
+
try {
|
|
1364
|
+
const provider = this.ProviderToUse;
|
|
1365
|
+
const engine = CommunicationEngineBase.GetProviderInstance(provider, CommunicationEngineBase);
|
|
1366
|
+
await engine.Config(false, provider.CurrentUser, provider);
|
|
1367
|
+
if (this._entityInfo !== entity) {
|
|
1368
|
+
return; // the grid moved to another entity while the engine loaded
|
|
1369
|
+
}
|
|
1370
|
+
this._entitySupportsCommunication = engine.Metadata.EntityCommunicationMessageTypes
|
|
1371
|
+
.some(m => m.IsActive && UUIDsEqual(m.EntityID, entity.ID));
|
|
1372
|
+
this.cdr.detectChanges();
|
|
1373
|
+
}
|
|
1374
|
+
catch {
|
|
1375
|
+
// Non-fatal: leave Send Message hidden, and retry on the next entity resolution.
|
|
1376
|
+
this._communicationCheckedForEntityID = null;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1328
1379
|
// ========================================
|
|
1329
1380
|
// Navigation Inputs
|
|
1330
1381
|
// ========================================
|
|
@@ -1426,8 +1477,9 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1426
1477
|
this._entityActionsAutoLoaded = true;
|
|
1427
1478
|
try {
|
|
1428
1479
|
const provider = this.ProviderToUse;
|
|
1429
|
-
|
|
1430
|
-
|
|
1480
|
+
const engine = this.entityActionEngine();
|
|
1481
|
+
await engine.Config(false, provider.CurrentUser, provider);
|
|
1482
|
+
const configs = this.mapEntityActionsToConfigs(engine, this._entityInfo.Name);
|
|
1431
1483
|
if (configs.length > 0) {
|
|
1432
1484
|
this._entityActions = configs;
|
|
1433
1485
|
this._showEntityActionButtons = true;
|
|
@@ -1439,9 +1491,12 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1439
1491
|
this._entityActionsAutoLoaded = false;
|
|
1440
1492
|
}
|
|
1441
1493
|
}
|
|
1494
|
+
/** The EntityAction engine for this grid's provider, so a multi-provider client reads the right server's actions. */
|
|
1495
|
+
entityActionEngine() {
|
|
1496
|
+
return EntityActionEngineBase.GetProviderInstance(this.ProviderToUse, EntityActionEngineBase);
|
|
1497
|
+
}
|
|
1442
1498
|
/** Maps the entity's active EntityActions to grid configs, carrying the driver key + RecordProcessID. */
|
|
1443
|
-
mapEntityActionsToConfigs(entityName) {
|
|
1444
|
-
const engine = EntityActionEngineBase.Instance;
|
|
1499
|
+
mapEntityActionsToConfigs(engine, entityName) {
|
|
1445
1500
|
return engine.GetActionsByEntityName(entityName, 'Active').map((ea) => {
|
|
1446
1501
|
const driverInvocation = engine.Invocations.find(i => UUIDsEqual(i.EntityActionID, ea.ID) && !!i.RuntimeUXDriverClass);
|
|
1447
1502
|
const recordProcessParam = engine.Params.find(p => UUIDsEqual(p.EntityActionID, ea.ID) && p.ActionParam?.trim().toLowerCase() === 'recordprocessid');
|
|
@@ -1495,14 +1550,14 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1495
1550
|
* Returns the effective aggregates config, preferring _aggregatesConfig but falling back to _gridState.aggregates.
|
|
1496
1551
|
* This ensures aggregates work regardless of whether they came from explicit config or from view's GridState.
|
|
1497
1552
|
*/
|
|
1498
|
-
get
|
|
1553
|
+
get effectiveAggregatesConfig() {
|
|
1499
1554
|
return this._aggregatesConfig || this._gridState?.aggregates;
|
|
1500
1555
|
}
|
|
1501
1556
|
/**
|
|
1502
1557
|
* Returns enabled aggregates configured for card display.
|
|
1503
1558
|
*/
|
|
1504
1559
|
get CardAggregates() {
|
|
1505
|
-
const config = this.
|
|
1560
|
+
const config = this.effectiveAggregatesConfig;
|
|
1506
1561
|
if (!config?.expressions)
|
|
1507
1562
|
return [];
|
|
1508
1563
|
return config.expressions
|
|
@@ -1513,7 +1568,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1513
1568
|
* Returns enabled aggregates configured for column footer display.
|
|
1514
1569
|
*/
|
|
1515
1570
|
get ColumnAggregates() {
|
|
1516
|
-
const config = this.
|
|
1571
|
+
const config = this.effectiveAggregatesConfig;
|
|
1517
1572
|
if (!config?.expressions)
|
|
1518
1573
|
return [];
|
|
1519
1574
|
return config.expressions
|
|
@@ -1537,7 +1592,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1537
1592
|
/**
|
|
1538
1593
|
* Get formatted aggregate value for display
|
|
1539
1594
|
*/
|
|
1540
|
-
|
|
1595
|
+
GetAggregateValue(agg) {
|
|
1541
1596
|
const key = agg.id || agg.expression;
|
|
1542
1597
|
const value = this._aggregateValues.get(key);
|
|
1543
1598
|
if (value == null)
|
|
@@ -1567,6 +1622,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1567
1622
|
}
|
|
1568
1623
|
return String(value);
|
|
1569
1624
|
}
|
|
1625
|
+
/** @deprecated Use {@link GetAggregateValue}. */
|
|
1626
|
+
getAggregateValue(agg) {
|
|
1627
|
+
return this.GetAggregateValue(agg);
|
|
1628
|
+
}
|
|
1570
1629
|
/**
|
|
1571
1630
|
* Whether an aggregate summarises a date-only column. An aggregate carries no field metadata of
|
|
1572
1631
|
* its own, so the column is read from a single-field expression such as `MIN(IntakeDate)` or
|
|
@@ -1702,18 +1761,42 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1702
1761
|
// ========================================
|
|
1703
1762
|
// View Children
|
|
1704
1763
|
// ========================================
|
|
1705
|
-
|
|
1764
|
+
GridContainer;
|
|
1765
|
+
/** @deprecated Use {@link GridContainer}. */
|
|
1766
|
+
get gridContainer() {
|
|
1767
|
+
return this.GridContainer;
|
|
1768
|
+
}
|
|
1769
|
+
/** @deprecated Use {@link GridContainer}. */
|
|
1770
|
+
set gridContainer(value) {
|
|
1771
|
+
this.GridContainer = value;
|
|
1772
|
+
}
|
|
1706
1773
|
// ========================================
|
|
1707
1774
|
// AG Grid Properties
|
|
1708
1775
|
// ========================================
|
|
1709
1776
|
/** AG Grid column definitions */
|
|
1710
|
-
|
|
1777
|
+
AgColumnDefs = [];
|
|
1778
|
+
/** @deprecated Use {@link AgColumnDefs}. */
|
|
1779
|
+
get agColumnDefs() {
|
|
1780
|
+
return this.AgColumnDefs;
|
|
1781
|
+
}
|
|
1782
|
+
/** @deprecated Use {@link AgColumnDefs}. */
|
|
1783
|
+
set agColumnDefs(value) {
|
|
1784
|
+
this.AgColumnDefs = value;
|
|
1785
|
+
}
|
|
1711
1786
|
/** AG Grid row data */
|
|
1712
|
-
|
|
1787
|
+
RowData = [];
|
|
1788
|
+
/** @deprecated Use {@link RowData}. */
|
|
1789
|
+
get rowData() {
|
|
1790
|
+
return this.RowData;
|
|
1791
|
+
}
|
|
1792
|
+
/** @deprecated Use {@link RowData}. */
|
|
1793
|
+
set rowData(value) {
|
|
1794
|
+
this.RowData = value;
|
|
1795
|
+
}
|
|
1713
1796
|
/** AG Grid API reference */
|
|
1714
1797
|
gridApi = null;
|
|
1715
1798
|
/** AG Grid theme (v34+) with custom selection colors */
|
|
1716
|
-
|
|
1799
|
+
AgGridTheme = themeAlpine.withParams({
|
|
1717
1800
|
backgroundColor: 'var(--mj-bg-surface)',
|
|
1718
1801
|
foregroundColor: 'var(--mj-text-primary)',
|
|
1719
1802
|
textColor: 'var(--mj-text-primary)',
|
|
@@ -1731,23 +1814,47 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1731
1814
|
borderRadius: 'var(--mj-radius-sm)',
|
|
1732
1815
|
browserColorScheme: 'inherit',
|
|
1733
1816
|
});
|
|
1817
|
+
/** @deprecated Use {@link AgGridTheme}. */
|
|
1818
|
+
get agGridTheme() {
|
|
1819
|
+
return this.AgGridTheme;
|
|
1820
|
+
}
|
|
1821
|
+
/** @deprecated Use {@link AgGridTheme}. */
|
|
1822
|
+
set agGridTheme(value) {
|
|
1823
|
+
this.AgGridTheme = value;
|
|
1824
|
+
}
|
|
1734
1825
|
/** AG Grid row selection configuration */
|
|
1735
|
-
|
|
1826
|
+
AgRowSelection = { mode: 'singleRow' };
|
|
1827
|
+
/** @deprecated Use {@link AgRowSelection}. */
|
|
1828
|
+
get agRowSelection() {
|
|
1829
|
+
return this.AgRowSelection;
|
|
1830
|
+
}
|
|
1831
|
+
/** @deprecated Use {@link AgRowSelection}. */
|
|
1832
|
+
set agRowSelection(value) {
|
|
1833
|
+
this.AgRowSelection = value;
|
|
1834
|
+
}
|
|
1736
1835
|
/** Default column settings */
|
|
1737
|
-
|
|
1836
|
+
DefaultColDef = {
|
|
1738
1837
|
sortable: true,
|
|
1739
1838
|
filter: false,
|
|
1740
1839
|
resizable: true,
|
|
1741
1840
|
minWidth: 80
|
|
1742
1841
|
};
|
|
1842
|
+
/** @deprecated Use {@link DefaultColDef}. */
|
|
1843
|
+
get defaultColDef() {
|
|
1844
|
+
return this.DefaultColDef;
|
|
1845
|
+
}
|
|
1846
|
+
/** @deprecated Use {@link DefaultColDef}. */
|
|
1847
|
+
set defaultColDef(value) {
|
|
1848
|
+
this.DefaultColDef = value;
|
|
1849
|
+
}
|
|
1743
1850
|
/**
|
|
1744
1851
|
* Update defaultColDef when text wrapping setting changes
|
|
1745
1852
|
* Enables/disables auto row height and cell text wrapping
|
|
1746
1853
|
*/
|
|
1747
1854
|
updateDefaultColDefForWrapping() {
|
|
1748
1855
|
if (this._wrapText) {
|
|
1749
|
-
this.
|
|
1750
|
-
...this.
|
|
1856
|
+
this.DefaultColDef = {
|
|
1857
|
+
...this.DefaultColDef,
|
|
1751
1858
|
wrapText: true,
|
|
1752
1859
|
autoHeight: true,
|
|
1753
1860
|
cellClass: 'cell-wrap-text'
|
|
@@ -1755,21 +1862,37 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1755
1862
|
}
|
|
1756
1863
|
else {
|
|
1757
1864
|
// Remove wrapping properties
|
|
1758
|
-
const { wrapText, autoHeight, cellClass, ...rest } = this.
|
|
1759
|
-
this.
|
|
1865
|
+
const { wrapText, autoHeight, cellClass, ...rest } = this.DefaultColDef;
|
|
1866
|
+
this.DefaultColDef = rest;
|
|
1760
1867
|
}
|
|
1761
1868
|
// Refresh the grid to apply changes
|
|
1762
1869
|
if (this.gridApi) {
|
|
1763
|
-
this.gridApi.setGridOption('defaultColDef', this.
|
|
1870
|
+
this.gridApi.setGridOption('defaultColDef', this.DefaultColDef);
|
|
1764
1871
|
this.gridApi.refreshCells({ force: true });
|
|
1765
1872
|
}
|
|
1766
1873
|
}
|
|
1767
1874
|
/** Get row ID function for AG Grid */
|
|
1768
|
-
|
|
1875
|
+
GetRowId = (params) => params.data['__pk'];
|
|
1876
|
+
/** @deprecated Use {@link GetRowId}. */
|
|
1877
|
+
get getRowId() {
|
|
1878
|
+
return this.GetRowId;
|
|
1879
|
+
}
|
|
1880
|
+
/** @deprecated Use {@link GetRowId}. */
|
|
1881
|
+
set getRowId(value) {
|
|
1882
|
+
this.GetRowId = value;
|
|
1883
|
+
}
|
|
1769
1884
|
/** Suppress sort changed events during programmatic updates */
|
|
1770
1885
|
suppressSortEvents = false;
|
|
1771
1886
|
/** AG Grid options for infinite scroll mode */
|
|
1772
|
-
|
|
1887
|
+
GridOptions = {};
|
|
1888
|
+
/** @deprecated Use {@link GridOptions}. */
|
|
1889
|
+
get gridOptions() {
|
|
1890
|
+
return this.GridOptions;
|
|
1891
|
+
}
|
|
1892
|
+
/** @deprecated Use {@link GridOptions}. */
|
|
1893
|
+
set gridOptions(value) {
|
|
1894
|
+
this.GridOptions = value;
|
|
1895
|
+
}
|
|
1773
1896
|
/** Datasource for infinite scroll mode */
|
|
1774
1897
|
infiniteDatasource = null;
|
|
1775
1898
|
// ========================================
|
|
@@ -1824,7 +1947,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1824
1947
|
return true;
|
|
1825
1948
|
}
|
|
1826
1949
|
// Loading state
|
|
1827
|
-
|
|
1950
|
+
Loading = false;
|
|
1951
|
+
/** @deprecated Use {@link Loading}. */
|
|
1952
|
+
get loading() {
|
|
1953
|
+
return this.Loading;
|
|
1954
|
+
}
|
|
1955
|
+
/** @deprecated Use {@link Loading}. */
|
|
1956
|
+
set loading(value) {
|
|
1957
|
+
this.Loading = value;
|
|
1958
|
+
}
|
|
1828
1959
|
_errorMessage = '';
|
|
1829
1960
|
/**
|
|
1830
1961
|
* Raw technical error from the last failed load. Kept for callers and the
|
|
@@ -1849,7 +1980,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1849
1980
|
const friendly = 'The server may be busy or briefly unreachable — retrying usually fixes this.';
|
|
1850
1981
|
return this._errorMessage ? `${friendly} (Detail: ${this._errorMessage})` : friendly;
|
|
1851
1982
|
}
|
|
1852
|
-
totalRowCount = 0;
|
|
1983
|
+
totalRowCount = 0; // case-violation-ok-legacy-back-compat: the PascalCase name is already taken in this scope
|
|
1853
1984
|
_loadDataPromise = null;
|
|
1854
1985
|
// Cleanup
|
|
1855
1986
|
destroy$ = new Subject();
|
|
@@ -1884,17 +2015,45 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
1884
2015
|
* For dynamic views, always true (persists to user settings).
|
|
1885
2016
|
* For saved views, depends on view ownership and permissions.
|
|
1886
2017
|
*/
|
|
1887
|
-
get
|
|
2018
|
+
get CanEditCurrentView() {
|
|
1888
2019
|
if (this.IsDynamicView) {
|
|
1889
2020
|
return true; // Dynamic views persist to user settings
|
|
1890
2021
|
}
|
|
1891
2022
|
return this._viewEntity?.UserCanEdit ?? false;
|
|
1892
2023
|
}
|
|
2024
|
+
/** @deprecated Use {@link CanEditCurrentView}. */
|
|
2025
|
+
get canEditCurrentView() {
|
|
2026
|
+
return this.CanEditCurrentView;
|
|
2027
|
+
}
|
|
1893
2028
|
// Overflow menu state
|
|
1894
|
-
|
|
2029
|
+
ShowOverflowMenu = false;
|
|
2030
|
+
/** @deprecated Use {@link ShowOverflowMenu}. */
|
|
2031
|
+
get showOverflowMenu() {
|
|
2032
|
+
return this.ShowOverflowMenu;
|
|
2033
|
+
}
|
|
2034
|
+
/** @deprecated Use {@link ShowOverflowMenu}. */
|
|
2035
|
+
set showOverflowMenu(value) {
|
|
2036
|
+
this.ShowOverflowMenu = value;
|
|
2037
|
+
}
|
|
1895
2038
|
// Export dialog state
|
|
1896
|
-
|
|
1897
|
-
|
|
2039
|
+
ShowExportDialog = false;
|
|
2040
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
2041
|
+
get showExportDialog() {
|
|
2042
|
+
return this.ShowExportDialog;
|
|
2043
|
+
}
|
|
2044
|
+
/** @deprecated Use {@link ShowExportDialog}. */
|
|
2045
|
+
set showExportDialog(value) {
|
|
2046
|
+
this.ShowExportDialog = value;
|
|
2047
|
+
}
|
|
2048
|
+
ExportDialogConfig = null;
|
|
2049
|
+
/** @deprecated Use {@link ExportDialogConfig}. */
|
|
2050
|
+
get exportDialogConfig() {
|
|
2051
|
+
return this.ExportDialogConfig;
|
|
2052
|
+
}
|
|
2053
|
+
/** @deprecated Use {@link ExportDialogConfig}. */
|
|
2054
|
+
set exportDialogConfig(value) {
|
|
2055
|
+
this.ExportDialogConfig = value;
|
|
2056
|
+
}
|
|
1898
2057
|
constructor(cdr, elementRef, exportService, ngZone) {
|
|
1899
2058
|
super();
|
|
1900
2059
|
this.cdr = cdr;
|
|
@@ -2078,6 +2237,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2078
2237
|
this._suppressPersist = false;
|
|
2079
2238
|
// Entity is resolved by now — self-load its actions if auto-load is enabled.
|
|
2080
2239
|
void this.maybeAutoLoadEntityActions();
|
|
2240
|
+
void this.resolveCommunicationSupport();
|
|
2081
2241
|
}
|
|
2082
2242
|
}
|
|
2083
2243
|
/**
|
|
@@ -2294,7 +2454,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2294
2454
|
this.buildAgColumnDefs();
|
|
2295
2455
|
// Update AG Grid with new column definitions to apply header styles
|
|
2296
2456
|
if (this.gridApi && !this.gridApi.isDestroyed()) {
|
|
2297
|
-
this.gridApi.setGridOption('columnDefs', this.
|
|
2457
|
+
this.gridApi.setGridOption('columnDefs', this.AgColumnDefs);
|
|
2298
2458
|
// Refresh header to apply new header styles
|
|
2299
2459
|
this.gridApi.refreshHeader();
|
|
2300
2460
|
}
|
|
@@ -2327,7 +2487,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2327
2487
|
if (this._gridState.aggregates) {
|
|
2328
2488
|
this._aggregatesConfig = this._gridState.aggregates;
|
|
2329
2489
|
// Fetch aggregate values when gridState aggregates change
|
|
2330
|
-
this.
|
|
2490
|
+
this.RefreshAggregates();
|
|
2331
2491
|
}
|
|
2332
2492
|
// Clear suppression after AG Grid's async events have been processed.
|
|
2333
2493
|
// setTimeout(0) fires after microtasks and rAF, catching all async sort events.
|
|
@@ -2339,8 +2499,8 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2339
2499
|
* Used when gridState changes and includes new aggregate config.
|
|
2340
2500
|
* This runs a RunView with MaxRows=0 to get only aggregate results.
|
|
2341
2501
|
*/
|
|
2342
|
-
async
|
|
2343
|
-
const effectiveAggConfig = this.
|
|
2502
|
+
async RefreshAggregates() {
|
|
2503
|
+
const effectiveAggConfig = this.effectiveAggregatesConfig;
|
|
2344
2504
|
if (!effectiveAggConfig?.expressions?.length) {
|
|
2345
2505
|
this._aggregateResults = [];
|
|
2346
2506
|
this._aggregateValues.clear();
|
|
@@ -2404,13 +2564,17 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2404
2564
|
});
|
|
2405
2565
|
}
|
|
2406
2566
|
}
|
|
2567
|
+
/** @deprecated Use {@link RefreshAggregates}. */
|
|
2568
|
+
async refreshAggregates() {
|
|
2569
|
+
return this.RefreshAggregates();
|
|
2570
|
+
}
|
|
2407
2571
|
onFilterTextChanged() {
|
|
2408
2572
|
// Rebuild column defs to update cell renderers with new filter text
|
|
2409
2573
|
this.buildAgColumnDefs();
|
|
2410
2574
|
// Update AG Grid with new column definitions and refresh cells
|
|
2411
2575
|
if (this.gridApi) {
|
|
2412
2576
|
// Update the column definitions in AG Grid
|
|
2413
|
-
this.gridApi.setGridOption('columnDefs', this.
|
|
2577
|
+
this.gridApi.setGridOption('columnDefs', this.AgColumnDefs);
|
|
2414
2578
|
// Force refresh all cells to apply new highlighting
|
|
2415
2579
|
this.gridApi.refreshCells({ force: true });
|
|
2416
2580
|
// Apply AG Grid quick filter to actually filter rows client-side
|
|
@@ -2661,16 +2825,16 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2661
2825
|
? this.buildAgColumnDefsFromGridState(this._gridState.columnSettings)
|
|
2662
2826
|
: [];
|
|
2663
2827
|
if (fromGridState.length > 0) {
|
|
2664
|
-
this.
|
|
2828
|
+
this.AgColumnDefs = fromGridState;
|
|
2665
2829
|
}
|
|
2666
2830
|
else if (this._columns.length > 0) {
|
|
2667
|
-
this.
|
|
2831
|
+
this.AgColumnDefs = this._columns.map(col => this.mapColumnConfigToColDef(col));
|
|
2668
2832
|
}
|
|
2669
2833
|
else if (this._entityInfo) {
|
|
2670
|
-
this.
|
|
2834
|
+
this.AgColumnDefs = this.generateAgColumnDefs(this._entityInfo);
|
|
2671
2835
|
}
|
|
2672
2836
|
else {
|
|
2673
|
-
this.
|
|
2837
|
+
this.AgColumnDefs = [];
|
|
2674
2838
|
}
|
|
2675
2839
|
// Field security, applied to EVERY branch above rather than inside each one. This is the
|
|
2676
2840
|
// only point all three column sources meet: a saved view's `columnSettings` (the usual path
|
|
@@ -2680,13 +2844,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2680
2844
|
// appended — those are synthetic and have no entity field to check.
|
|
2681
2845
|
const deniedReadFields = this.deniedReadFieldsFor(this._entityInfo);
|
|
2682
2846
|
if (deniedReadFields.size > 0) {
|
|
2683
|
-
this.
|
|
2847
|
+
this.AgColumnDefs = this.AgColumnDefs.filter(c => !c.field || !deniedReadFields.has(c.field.trim().toLowerCase()));
|
|
2684
2848
|
}
|
|
2685
2849
|
// Add row number column if enabled
|
|
2686
|
-
if (this._showRowNumbers && this.
|
|
2687
|
-
this.
|
|
2850
|
+
if (this._showRowNumbers && this.AgColumnDefs.length > 0) {
|
|
2851
|
+
this.AgColumnDefs.unshift({
|
|
2688
2852
|
headerName: '#',
|
|
2689
|
-
field:
|
|
2853
|
+
field: ROW_NUMBER_FIELD,
|
|
2690
2854
|
width: 60,
|
|
2691
2855
|
minWidth: 50,
|
|
2692
2856
|
maxWidth: 80,
|
|
@@ -2695,8 +2859,8 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2695
2859
|
valueGetter: (params) => params.node ? params.node.rowIndex + 1 : ''
|
|
2696
2860
|
});
|
|
2697
2861
|
}
|
|
2698
|
-
if (this._fillWidth && this.
|
|
2699
|
-
this.
|
|
2862
|
+
if (this._fillWidth && this.AgColumnDefs.length > 0) {
|
|
2863
|
+
this.AgColumnDefs.push(this.buildFillerColumnDef());
|
|
2700
2864
|
}
|
|
2701
2865
|
}
|
|
2702
2866
|
/**
|
|
@@ -2765,7 +2929,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2765
2929
|
if (!this._columnsFromHost || !this._entityInfo || this._columns.length === 0) {
|
|
2766
2930
|
return false;
|
|
2767
2931
|
}
|
|
2768
|
-
const next =
|
|
2932
|
+
const next = CanonicalizeColumnFields(this._entityInfo, this._columns);
|
|
2769
2933
|
const changed = next.some((col, i) => col !== this._columns[i]);
|
|
2770
2934
|
if (changed) {
|
|
2771
2935
|
this._columns = next;
|
|
@@ -2986,7 +3150,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
2986
3150
|
const fieldType = field.TSType;
|
|
2987
3151
|
const fieldNameLower = field.Name.toLowerCase();
|
|
2988
3152
|
const extendedType = field.ExtendedType?.toLowerCase() || '';
|
|
2989
|
-
const vc = this.
|
|
3153
|
+
const vc = this.EffectiveVisualConfig;
|
|
2990
3154
|
// Determine special field types using ExtendedType metadata first, then field name patterns as fallback
|
|
2991
3155
|
const isCurrency = customFormat?.type === 'currency' ||
|
|
2992
3156
|
fieldNameLower.includes('amount') ||
|
|
@@ -3401,17 +3565,17 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3401
3565
|
updateAgRowSelection() {
|
|
3402
3566
|
switch (this._selectionMode) {
|
|
3403
3567
|
case 'none':
|
|
3404
|
-
this.
|
|
3568
|
+
this.AgRowSelection = { mode: 'singleRow', enableClickSelection: false, checkboxes: false };
|
|
3405
3569
|
break;
|
|
3406
3570
|
case 'single':
|
|
3407
|
-
this.
|
|
3571
|
+
this.AgRowSelection = { mode: 'singleRow', enableClickSelection: true, checkboxes: false };
|
|
3408
3572
|
break;
|
|
3409
3573
|
case 'multiple':
|
|
3410
3574
|
// enableSelectionWithoutKeys replaces deprecated rowMultiSelectWithClick (as of AG Grid v32.2)
|
|
3411
|
-
this.
|
|
3575
|
+
this.AgRowSelection = { mode: 'multiRow', enableClickSelection: true, checkboxes: false, enableSelectionWithoutKeys: true };
|
|
3412
3576
|
break;
|
|
3413
3577
|
case 'checkbox':
|
|
3414
|
-
this.
|
|
3578
|
+
this.AgRowSelection = {
|
|
3415
3579
|
mode: 'multiRow',
|
|
3416
3580
|
enableClickSelection: true,
|
|
3417
3581
|
checkboxes: true,
|
|
@@ -3422,7 +3586,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3422
3586
|
}
|
|
3423
3587
|
// Update the grid if it's already initialized
|
|
3424
3588
|
if (this.gridApi) {
|
|
3425
|
-
this.gridApi.setGridOption('rowSelection', this.
|
|
3589
|
+
this.gridApi.setGridOption('rowSelection', this.AgRowSelection);
|
|
3426
3590
|
}
|
|
3427
3591
|
}
|
|
3428
3592
|
// ========================================
|
|
@@ -3521,7 +3685,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3521
3685
|
runViewParams.UserSearchString = beforeLoadEvent.modifiedParams.searchString;
|
|
3522
3686
|
}
|
|
3523
3687
|
}
|
|
3524
|
-
this.
|
|
3688
|
+
this.Loading = true;
|
|
3525
3689
|
this._aggregatesLoading = true;
|
|
3526
3690
|
this.errorMessage = '';
|
|
3527
3691
|
this.cdr.detectChanges();
|
|
@@ -3529,7 +3693,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3529
3693
|
try {
|
|
3530
3694
|
// Build aggregate expressions from config if present
|
|
3531
3695
|
// Use EffectiveAggregatesConfig to check both _aggregatesConfig and _gridState.aggregates
|
|
3532
|
-
const effectiveAggConfig = this.
|
|
3696
|
+
const effectiveAggConfig = this.effectiveAggregatesConfig;
|
|
3533
3697
|
let aggregateExpressions;
|
|
3534
3698
|
if (effectiveAggConfig?.expressions?.length) {
|
|
3535
3699
|
aggregateExpressions = effectiveAggConfig.expressions
|
|
@@ -3543,7 +3707,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3543
3707
|
const result = await rv.RunView({
|
|
3544
3708
|
...runViewParams,
|
|
3545
3709
|
ResultType: 'simple',
|
|
3546
|
-
Fields: this._entityInfo ?
|
|
3710
|
+
Fields: this._entityInfo ? ComputeFieldsList(this._entityInfo, this._gridState, this.hostColumnFieldNames()) : undefined,
|
|
3547
3711
|
Aggregates: aggregateExpressions
|
|
3548
3712
|
});
|
|
3549
3713
|
const loadTimeMs = performance.now() - startTime;
|
|
@@ -3580,7 +3744,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3580
3744
|
}
|
|
3581
3745
|
finally {
|
|
3582
3746
|
this.ngZone.run(() => {
|
|
3583
|
-
this.
|
|
3747
|
+
this.Loading = false;
|
|
3584
3748
|
this.cdr.detectChanges();
|
|
3585
3749
|
});
|
|
3586
3750
|
}
|
|
@@ -3636,7 +3800,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3636
3800
|
const result = await rv.RunView({
|
|
3637
3801
|
...runViewParams,
|
|
3638
3802
|
ResultType: 'simple',
|
|
3639
|
-
Fields: this._entityInfo ?
|
|
3803
|
+
Fields: this._entityInfo ? ComputeFieldsList(this._entityInfo, this._gridState, this.hostColumnFieldNames()) : undefined
|
|
3640
3804
|
});
|
|
3641
3805
|
if (result.Success) {
|
|
3642
3806
|
const entities = result.Results || [];
|
|
@@ -3729,7 +3893,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3729
3893
|
processData() {
|
|
3730
3894
|
this._rowDataMap.clear();
|
|
3731
3895
|
const dataSource = this._useExternalData ? this._data : this._allData;
|
|
3732
|
-
this.
|
|
3896
|
+
this.RowData = dataSource.map((entity, index) => {
|
|
3733
3897
|
const key = this.getRowKey(entity);
|
|
3734
3898
|
const rowData = {
|
|
3735
3899
|
key,
|
|
@@ -3790,7 +3954,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3790
3954
|
getRowKey(entity) {
|
|
3791
3955
|
// Build key from EntityInfo PK fields when available
|
|
3792
3956
|
if (this._entityInfo) {
|
|
3793
|
-
return
|
|
3957
|
+
return BuildPkString(entity, this._entityInfo);
|
|
3794
3958
|
}
|
|
3795
3959
|
// Fallback to configured key field via direct property access
|
|
3796
3960
|
const keyValue = entity[this._keyField];
|
|
@@ -3806,7 +3970,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3806
3970
|
// ========================================
|
|
3807
3971
|
// AG Grid Event Handlers
|
|
3808
3972
|
// ========================================
|
|
3809
|
-
|
|
3973
|
+
OnGridReady(event) {
|
|
3810
3974
|
this.gridApi = event.api;
|
|
3811
3975
|
// Let the quick filter (search box) match values in HIDDEN columns too (bug C2). AG Grid
|
|
3812
3976
|
// defaults `includeHiddenColumnsInQuickFilter` to false, so without this a search term that
|
|
@@ -3825,6 +3989,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3825
3989
|
this.setupInfiniteScroll();
|
|
3826
3990
|
}
|
|
3827
3991
|
}
|
|
3992
|
+
/** @deprecated Use {@link OnGridReady}. */
|
|
3993
|
+
onGridReady(event) {
|
|
3994
|
+
return this.OnGridReady(event);
|
|
3995
|
+
}
|
|
3828
3996
|
/**
|
|
3829
3997
|
* Smart column auto-sizing that respects our width estimates
|
|
3830
3998
|
* while ensuring the grid fills available space appropriately
|
|
@@ -3834,10 +4002,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3834
4002
|
// The proportional stretch below would fight that: sizeColumnsToFit() assigns explicit widths to
|
|
3835
4003
|
// every column, which clears the filler's flex and re-introduces the stretching of real columns
|
|
3836
4004
|
// that the filler exists to avoid.
|
|
3837
|
-
if (this.
|
|
4005
|
+
if (this.AgColumnDefs.some(col => col.flex))
|
|
3838
4006
|
return;
|
|
3839
4007
|
// Get total estimated width from our column definitions
|
|
3840
|
-
const totalEstimatedWidth = this.
|
|
4008
|
+
const totalEstimatedWidth = this.AgColumnDefs.reduce((sum, col) => sum + (col.width || 150), 0);
|
|
3841
4009
|
// Get available width from the grid container
|
|
3842
4010
|
const gridElement = this.elementRef.nativeElement.querySelector('.mj-ag-grid');
|
|
3843
4011
|
const availableWidth = gridElement?.clientWidth || 0;
|
|
@@ -3858,7 +4026,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3858
4026
|
// If our columns are wider than available space, let the user scroll horizontally
|
|
3859
4027
|
// (AG Grid handles this automatically)
|
|
3860
4028
|
}
|
|
3861
|
-
|
|
4029
|
+
OnAgRowClicked(event) {
|
|
3862
4030
|
if (!this._entityInfo || !event.data)
|
|
3863
4031
|
return;
|
|
3864
4032
|
const pkString = event.data['__pk'];
|
|
@@ -3879,7 +4047,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3879
4047
|
this.emitNavigationRequest(rowData.entity, pkString);
|
|
3880
4048
|
}
|
|
3881
4049
|
}
|
|
3882
|
-
|
|
4050
|
+
/** @deprecated Use {@link OnAgRowClicked}. */
|
|
4051
|
+
onAgRowClicked(event) {
|
|
4052
|
+
return this.OnAgRowClicked(event);
|
|
4053
|
+
}
|
|
4054
|
+
OnAgRowDoubleClicked(event) {
|
|
3883
4055
|
if (!this._entityInfo || !event.data)
|
|
3884
4056
|
return;
|
|
3885
4057
|
const pkString = event.data['__pk'];
|
|
@@ -3900,12 +4072,16 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3900
4072
|
this.emitNavigationRequest(rowData.entity, pkString);
|
|
3901
4073
|
}
|
|
3902
4074
|
}
|
|
4075
|
+
/** @deprecated Use {@link OnAgRowDoubleClicked}. */
|
|
4076
|
+
onAgRowDoubleClicked(event) {
|
|
4077
|
+
return this.OnAgRowDoubleClicked(event);
|
|
4078
|
+
}
|
|
3903
4079
|
/**
|
|
3904
4080
|
* Handles cell click events to detect FK link clicks.
|
|
3905
4081
|
* When a user clicks on a foreign key link, emits ForeignKeyClick event
|
|
3906
4082
|
* for the parent component to handle navigation.
|
|
3907
4083
|
*/
|
|
3908
|
-
|
|
4084
|
+
OnAgCellClicked(event) {
|
|
3909
4085
|
// Check if the click was on an FK link
|
|
3910
4086
|
const target = event.event?.target;
|
|
3911
4087
|
if (!target) {
|
|
@@ -3935,6 +4111,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3935
4111
|
//console.log('[FK Debug] Missing required data attributes, not emitting');
|
|
3936
4112
|
}
|
|
3937
4113
|
}
|
|
4114
|
+
/** @deprecated Use {@link OnAgCellClicked}. */
|
|
4115
|
+
onAgCellClicked(event) {
|
|
4116
|
+
return this.OnAgCellClicked(event);
|
|
4117
|
+
}
|
|
3938
4118
|
/**
|
|
3939
4119
|
* Emits a navigation request for the given entity record.
|
|
3940
4120
|
*/
|
|
@@ -3947,7 +4127,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3947
4127
|
compositeKey
|
|
3948
4128
|
});
|
|
3949
4129
|
}
|
|
3950
|
-
|
|
4130
|
+
OnAgSortChanged(event) {
|
|
3951
4131
|
if (this.suppressSortEvents) {
|
|
3952
4132
|
return;
|
|
3953
4133
|
}
|
|
@@ -3995,7 +4175,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
3995
4175
|
this.emitGridStateChanged('sort');
|
|
3996
4176
|
}
|
|
3997
4177
|
}
|
|
3998
|
-
|
|
4178
|
+
/** @deprecated Use {@link OnAgSortChanged}. */
|
|
4179
|
+
onAgSortChanged(event) {
|
|
4180
|
+
return this.OnAgSortChanged(event);
|
|
4181
|
+
}
|
|
4182
|
+
OnAgSelectionChanged(event) {
|
|
3999
4183
|
const selectedNodes = event.api.getSelectedNodes();
|
|
4000
4184
|
const previousSelection = [...this._selectedKeys];
|
|
4001
4185
|
const newSelection = selectedNodes.map(node => node.data['__pk']);
|
|
@@ -4039,20 +4223,32 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4039
4223
|
}
|
|
4040
4224
|
this.SelectionChange.emit(this._selectedKeys);
|
|
4041
4225
|
}
|
|
4042
|
-
|
|
4226
|
+
/** @deprecated Use {@link OnAgSelectionChanged}. */
|
|
4227
|
+
onAgSelectionChanged(event) {
|
|
4228
|
+
return this.OnAgSelectionChanged(event);
|
|
4229
|
+
}
|
|
4230
|
+
OnAgColumnResized(event) {
|
|
4043
4231
|
if (event.finished && event.source !== 'api') {
|
|
4044
4232
|
// User manually resized a column - mark as dirty
|
|
4045
4233
|
this._isGridStateDirty = true;
|
|
4046
4234
|
this.emitGridStateChanged('columns');
|
|
4047
4235
|
}
|
|
4048
4236
|
}
|
|
4049
|
-
|
|
4237
|
+
/** @deprecated Use {@link OnAgColumnResized}. */
|
|
4238
|
+
onAgColumnResized(event) {
|
|
4239
|
+
return this.OnAgColumnResized(event);
|
|
4240
|
+
}
|
|
4241
|
+
OnAgColumnMoved(event) {
|
|
4050
4242
|
if (event.finished && event.source !== 'api') {
|
|
4051
4243
|
// User manually moved a column - mark as dirty
|
|
4052
4244
|
this._isGridStateDirty = true;
|
|
4053
4245
|
this.emitGridStateChanged('columns');
|
|
4054
4246
|
}
|
|
4055
4247
|
}
|
|
4248
|
+
/** @deprecated Use {@link OnAgColumnMoved}. */
|
|
4249
|
+
onAgColumnMoved(event) {
|
|
4250
|
+
return this.OnAgColumnMoved(event);
|
|
4251
|
+
}
|
|
4056
4252
|
// ========================================
|
|
4057
4253
|
// Grid State Management
|
|
4058
4254
|
// ========================================
|
|
@@ -4072,7 +4268,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4072
4268
|
});
|
|
4073
4269
|
// Schedule auto-persist if enabled, but NOT during view transitions
|
|
4074
4270
|
// (suppressPersist prevents old view's state from being saved to new view)
|
|
4075
|
-
if (this._autoPersistState && !this._suppressPersist && this.
|
|
4271
|
+
if (this._autoPersistState && !this._suppressPersist && this.CanEditCurrentView) {
|
|
4076
4272
|
if (!this.IsDynamicView && this._viewEntity) {
|
|
4077
4273
|
// Stored view - persist to UserView.GridState (debounced)
|
|
4078
4274
|
// Track pending state for flush on destroy
|
|
@@ -4164,7 +4360,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4164
4360
|
const sortedColumns = [];
|
|
4165
4361
|
for (let i = 0; i < columnState.length; i++) {
|
|
4166
4362
|
const col = columnState[i];
|
|
4167
|
-
if (col.colId ===
|
|
4363
|
+
if (col.colId === ROW_NUMBER_FIELD)
|
|
4168
4364
|
continue; // Skip row number column
|
|
4169
4365
|
// Case-insensitive: `colId` originates from a colDef's `field`, which for host-supplied
|
|
4170
4366
|
// columns is whatever the page wrote. `canonicalizeHostColumns()` normally settles that
|
|
@@ -4384,7 +4580,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4384
4580
|
this._data = [];
|
|
4385
4581
|
this._rowDataMap.clear();
|
|
4386
4582
|
this._selectedKeys = [];
|
|
4387
|
-
this.
|
|
4583
|
+
this.RowData = [];
|
|
4388
4584
|
this.totalRowCount = 0;
|
|
4389
4585
|
this.cdr.detectChanges();
|
|
4390
4586
|
}
|
|
@@ -4456,7 +4652,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4456
4652
|
// ========================================
|
|
4457
4653
|
// Toolbar Click Handlers
|
|
4458
4654
|
// ========================================
|
|
4459
|
-
|
|
4655
|
+
OnAddClick() {
|
|
4460
4656
|
// Emit legacy events for backward compatibility
|
|
4461
4657
|
this.AddRequested.emit();
|
|
4462
4658
|
this.NewButtonClick.emit();
|
|
@@ -4476,19 +4672,31 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4476
4672
|
}
|
|
4477
4673
|
}
|
|
4478
4674
|
}
|
|
4479
|
-
|
|
4675
|
+
/** @deprecated Use {@link OnAddClick}. */
|
|
4676
|
+
onAddClick() {
|
|
4677
|
+
return this.OnAddClick();
|
|
4678
|
+
}
|
|
4679
|
+
OnDeleteClick() {
|
|
4480
4680
|
const selectedRows = this.GetSelectedRows();
|
|
4481
4681
|
if (selectedRows.length > 0) {
|
|
4482
4682
|
this.DeleteRequested.emit(selectedRows);
|
|
4483
4683
|
this.DeleteButtonClick.emit(selectedRows);
|
|
4484
4684
|
}
|
|
4485
4685
|
}
|
|
4486
|
-
|
|
4686
|
+
/** @deprecated Use {@link OnDeleteClick}. */
|
|
4687
|
+
onDeleteClick() {
|
|
4688
|
+
return this.OnDeleteClick();
|
|
4689
|
+
}
|
|
4690
|
+
OnExportClick() {
|
|
4487
4691
|
this.ExportRequested.emit();
|
|
4488
4692
|
this.ExportButtonClick.emit();
|
|
4489
4693
|
// Show the export dialog
|
|
4490
4694
|
void this.showExportDialogForCurrentData();
|
|
4491
4695
|
}
|
|
4696
|
+
/** @deprecated Use {@link OnExportClick}. */
|
|
4697
|
+
onExportClick() {
|
|
4698
|
+
return this.OnExportClick();
|
|
4699
|
+
}
|
|
4492
4700
|
/**
|
|
4493
4701
|
* Shows the export dialog for the full result set (falling back to the loaded page when no
|
|
4494
4702
|
* ExportDataProvider is supplied).
|
|
@@ -4503,9 +4711,9 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4503
4711
|
finally {
|
|
4504
4712
|
this.IsPreparingExport = false;
|
|
4505
4713
|
}
|
|
4506
|
-
const columns = this.
|
|
4714
|
+
const columns = this.GetExportColumns();
|
|
4507
4715
|
const fileName = this.getDefaultExportFileName();
|
|
4508
|
-
this.
|
|
4716
|
+
this.ExportDialogConfig = {
|
|
4509
4717
|
data,
|
|
4510
4718
|
columns,
|
|
4511
4719
|
defaultFileName: fileName,
|
|
@@ -4515,17 +4723,21 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4515
4723
|
defaultSamplingMode: 'all',
|
|
4516
4724
|
dialogTitle: `Export ${this._entityInfo?.Name || 'Data'}`
|
|
4517
4725
|
};
|
|
4518
|
-
this.
|
|
4726
|
+
this.ShowExportDialog = true;
|
|
4519
4727
|
this.cdr.detectChanges();
|
|
4520
4728
|
}
|
|
4521
4729
|
/**
|
|
4522
4730
|
* Handle export dialog close
|
|
4523
4731
|
*/
|
|
4524
|
-
|
|
4525
|
-
this.
|
|
4526
|
-
this.
|
|
4732
|
+
OnExportDialogClosed(result) {
|
|
4733
|
+
this.ShowExportDialog = false;
|
|
4734
|
+
this.ExportDialogConfig = null;
|
|
4527
4735
|
this.cdr.detectChanges();
|
|
4528
4736
|
}
|
|
4737
|
+
/** @deprecated Use {@link OnExportDialogClosed}. */
|
|
4738
|
+
onExportDialogClosed(result) {
|
|
4739
|
+
return this.OnExportDialogClosed(result);
|
|
4740
|
+
}
|
|
4529
4741
|
/**
|
|
4530
4742
|
* Export grid data directly without showing dialog.
|
|
4531
4743
|
* Use this for programmatic export with specific options.
|
|
@@ -4535,7 +4747,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4535
4747
|
*/
|
|
4536
4748
|
async Export(options, download = true) {
|
|
4537
4749
|
const data = await this.resolveExportData();
|
|
4538
|
-
const columns = this.
|
|
4750
|
+
const columns = this.GetExportColumns();
|
|
4539
4751
|
const fileName = options?.fileName || this.getDefaultExportFileName();
|
|
4540
4752
|
const exportOptions = {
|
|
4541
4753
|
format: 'excel',
|
|
@@ -4595,59 +4807,42 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4595
4807
|
}
|
|
4596
4808
|
}
|
|
4597
4809
|
// rowData is already plain Record<string, unknown>[] objects - return directly
|
|
4598
|
-
return this.
|
|
4599
|
-
}
|
|
4600
|
-
/**
|
|
4601
|
-
*
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
//
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
.
|
|
4810
|
+
return this.RowData.map(row => row);
|
|
4811
|
+
}
|
|
4812
|
+
/**
|
|
4813
|
+
* The columns the grid is showing, in the order it shows them, with the headers it shows. This is the
|
|
4814
|
+
* single source for every export path — the toolbar's Export button, {@link Export}, and hosts such as
|
|
4815
|
+
* the view workspace — so an export always matches the screen. It reads the rendered AG Grid columns
|
|
4816
|
+
* rather than the host-declared `Columns` or a saved grid state, because either of those can differ
|
|
4817
|
+
* from the screen in order, visibility or header text. Before the grid has rendered, it falls back to
|
|
4818
|
+
* the column definitions it is about to render.
|
|
4819
|
+
*/
|
|
4820
|
+
GetExportColumns() {
|
|
4821
|
+
const displayed = this.gridApi?.getAllDisplayedColumns() ?? [];
|
|
4822
|
+
const defs = displayed.length > 0
|
|
4823
|
+
? displayed.map(c => c.getColDef())
|
|
4824
|
+
: this.AgColumnDefs.filter(d => !d.hide);
|
|
4825
|
+
// No `width`: ExportColumn.width is in characters, while grid widths are pixels, so passing them
|
|
4826
|
+
// made a 150px column 150 characters wide. Left unset, the Excel exporter auto-fits each column.
|
|
4827
|
+
return defs
|
|
4828
|
+
.filter(def => this.isExportableColumn(def))
|
|
4829
|
+
.map(def => {
|
|
4830
|
+
const colField = def.field;
|
|
4617
4831
|
// Case-insensitive, matching how col defs and auto-width resolve a host's field name.
|
|
4618
|
-
|
|
4619
|
-
const field = this._entityInfo?.Fields.find(f => f.Name.toLowerCase() === col.field.toLowerCase());
|
|
4832
|
+
const field = this._entityInfo?.Fields.find(f => f.Name.toLowerCase() === colField.toLowerCase());
|
|
4620
4833
|
return {
|
|
4621
4834
|
// The entity's spelling, because `name` is the KEY the export engine reads each row by
|
|
4622
4835
|
// (`row[col.name]`) and rows are keyed from entity metadata. The host's spelling here
|
|
4623
4836
|
// exported a correctly-headed column of blank cells.
|
|
4624
|
-
name: field?.Name ??
|
|
4625
|
-
displayName:
|
|
4626
|
-
dataType:
|
|
4627
|
-
width: typeof col.width === 'number' ? col.width : undefined
|
|
4837
|
+
name: field?.Name ?? colField,
|
|
4838
|
+
displayName: def.headerName || field?.DisplayNameOrName || colField,
|
|
4839
|
+
dataType: ExportColumnTypeForSQLType(field?.Type)
|
|
4628
4840
|
};
|
|
4629
4841
|
});
|
|
4630
4842
|
}
|
|
4631
|
-
/**
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
mapFieldTypeToExportType(fieldType) {
|
|
4635
|
-
if (!fieldType)
|
|
4636
|
-
return 'string';
|
|
4637
|
-
const type = fieldType.toLowerCase();
|
|
4638
|
-
if (type.includes('int') || type.includes('decimal') || type.includes('float') || type.includes('numeric')) {
|
|
4639
|
-
return 'number';
|
|
4640
|
-
}
|
|
4641
|
-
if (type.includes('date') || type.includes('time')) {
|
|
4642
|
-
return 'date';
|
|
4643
|
-
}
|
|
4644
|
-
if (type.includes('bit') || type.includes('bool')) {
|
|
4645
|
-
return 'boolean';
|
|
4646
|
-
}
|
|
4647
|
-
if (type.includes('money') || type.includes('currency')) {
|
|
4648
|
-
return 'currency';
|
|
4649
|
-
}
|
|
4650
|
-
return 'string';
|
|
4843
|
+
/** A data column: not the layout filler, not the row-number column, and bound to a field. */
|
|
4844
|
+
isExportableColumn(def) {
|
|
4845
|
+
return !!def.field && def.field !== ROW_NUMBER_FIELD && def.colId !== FILLER_COLUMN_ID;
|
|
4651
4846
|
}
|
|
4652
4847
|
/**
|
|
4653
4848
|
* Generate default file name for export
|
|
@@ -4657,11 +4852,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4657
4852
|
const timestamp = new Date().toISOString().slice(0, 10);
|
|
4658
4853
|
return `${entityName}_${timestamp}`;
|
|
4659
4854
|
}
|
|
4660
|
-
|
|
4855
|
+
OnRefreshClick() {
|
|
4661
4856
|
this.RefreshButtonClick.emit();
|
|
4662
4857
|
this.Refresh();
|
|
4663
4858
|
}
|
|
4664
|
-
|
|
4859
|
+
/** @deprecated Use {@link OnRefreshClick}. */
|
|
4860
|
+
onRefreshClick() {
|
|
4861
|
+
return this.OnRefreshClick();
|
|
4862
|
+
}
|
|
4863
|
+
OnCompareClick() {
|
|
4665
4864
|
const selectedRows = this.GetSelectedRows();
|
|
4666
4865
|
if (selectedRows.length >= 2) {
|
|
4667
4866
|
// Emit legacy event for backward compatibility
|
|
@@ -4675,7 +4874,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4675
4874
|
}
|
|
4676
4875
|
}
|
|
4677
4876
|
}
|
|
4678
|
-
|
|
4877
|
+
/** @deprecated Use {@link OnCompareClick}. */
|
|
4878
|
+
onCompareClick() {
|
|
4879
|
+
return this.OnCompareClick();
|
|
4880
|
+
}
|
|
4881
|
+
OnMergeClick() {
|
|
4679
4882
|
const selectedRows = this.GetSelectedRows();
|
|
4680
4883
|
if (selectedRows.length >= 2) {
|
|
4681
4884
|
// Emit legacy event for backward compatibility
|
|
@@ -4689,7 +4892,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4689
4892
|
}
|
|
4690
4893
|
}
|
|
4691
4894
|
}
|
|
4692
|
-
|
|
4895
|
+
/** @deprecated Use {@link OnMergeClick}. */
|
|
4896
|
+
onMergeClick() {
|
|
4897
|
+
return this.OnMergeClick();
|
|
4898
|
+
}
|
|
4899
|
+
async OnAddToListClick() {
|
|
4693
4900
|
let rows = this.GetSelectedRows();
|
|
4694
4901
|
// No explicit selection → add EVERY matching record, not just the loaded page. Without this,
|
|
4695
4902
|
// building a list from a sizeable view meant selecting-all page by page (bug E3). We fetch the
|
|
@@ -4715,7 +4922,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4715
4922
|
// Emit new structured event with record IDs for list management
|
|
4716
4923
|
if (this._entityInfo) {
|
|
4717
4924
|
const recordIds = rows.map(r => {
|
|
4718
|
-
return
|
|
4925
|
+
return BuildPkString(r, this._entityInfo);
|
|
4719
4926
|
});
|
|
4720
4927
|
this.AddToListRequested.emit({
|
|
4721
4928
|
entityInfo: this._entityInfo,
|
|
@@ -4725,7 +4932,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4725
4932
|
}
|
|
4726
4933
|
}
|
|
4727
4934
|
}
|
|
4728
|
-
|
|
4935
|
+
/** @deprecated Use {@link OnAddToListClick}. */
|
|
4936
|
+
async onAddToListClick() {
|
|
4937
|
+
return this.OnAddToListClick();
|
|
4938
|
+
}
|
|
4939
|
+
OnDuplicateSearchClick() {
|
|
4729
4940
|
const selectedRows = this.GetSelectedRows();
|
|
4730
4941
|
if (selectedRows.length >= 2) {
|
|
4731
4942
|
// Emit legacy event for backward compatibility
|
|
@@ -4739,7 +4950,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4739
4950
|
}
|
|
4740
4951
|
}
|
|
4741
4952
|
}
|
|
4742
|
-
|
|
4953
|
+
/** @deprecated Use {@link OnDuplicateSearchClick}. */
|
|
4954
|
+
onDuplicateSearchClick() {
|
|
4955
|
+
return this.OnDuplicateSearchClick();
|
|
4956
|
+
}
|
|
4957
|
+
OnCommunicationClick() {
|
|
4743
4958
|
const selectedRows = this.GetSelectedRows();
|
|
4744
4959
|
if (selectedRows.length > 0) {
|
|
4745
4960
|
// Emit legacy event for backward compatibility
|
|
@@ -4754,6 +4969,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4754
4969
|
}
|
|
4755
4970
|
}
|
|
4756
4971
|
}
|
|
4972
|
+
/** @deprecated Use {@link OnCommunicationClick}. */
|
|
4973
|
+
onCommunicationClick() {
|
|
4974
|
+
return this.OnCommunicationClick();
|
|
4975
|
+
}
|
|
4757
4976
|
/**
|
|
4758
4977
|
* "Manage Columns" / column-chooser toolbar affordance. The grid is generic and doesn't own a
|
|
4759
4978
|
* column-management UI, so it raises {@link ManageColumnsRequested} for its host to handle —
|
|
@@ -4761,9 +4980,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4761
4980
|
* column editor backed by `UserView.GridState`. Hosts that embed the grid standalone can handle
|
|
4762
4981
|
* this to surface their own column UI.
|
|
4763
4982
|
*/
|
|
4764
|
-
|
|
4983
|
+
OnColumnChooserClick() {
|
|
4765
4984
|
this.ManageColumnsRequested.emit();
|
|
4766
4985
|
}
|
|
4986
|
+
/** @deprecated Use {@link OnColumnChooserClick}. */
|
|
4987
|
+
onColumnChooserClick() {
|
|
4988
|
+
return this.OnColumnChooserClick();
|
|
4989
|
+
}
|
|
4767
4990
|
/**
|
|
4768
4991
|
* The runtime-UX driver currently mounted over the grid (e.g. the Record Process bulk-update runner),
|
|
4769
4992
|
* or null when none. Set when an action whose invocation names a `RuntimeUXDriverClass` is clicked.
|
|
@@ -4774,7 +4997,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4774
4997
|
* grid mounts that interactive driver in-place (no parent wiring required); otherwise it emits
|
|
4775
4998
|
* `EntityActionRequested` for the host to invoke the action the classic way.
|
|
4776
4999
|
*/
|
|
4777
|
-
|
|
5000
|
+
OnEntityActionClick(action) {
|
|
4778
5001
|
if (!this._entityInfo)
|
|
4779
5002
|
return;
|
|
4780
5003
|
if (action.runtimeUXDriverClass) {
|
|
@@ -4787,6 +5010,10 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4787
5010
|
selectedRecords: this.GetSelectedRows()
|
|
4788
5011
|
});
|
|
4789
5012
|
}
|
|
5013
|
+
/** @deprecated Use {@link OnEntityActionClick}. */
|
|
5014
|
+
onEntityActionClick(action) {
|
|
5015
|
+
return this.OnEntityActionClick(action);
|
|
5016
|
+
}
|
|
4790
5017
|
/** Builds the driver context from the current entity + selection and mounts the named driver. */
|
|
4791
5018
|
mountRuntimeDriver(action) {
|
|
4792
5019
|
const entity = this._entityInfo;
|
|
@@ -4823,7 +5050,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4823
5050
|
/**
|
|
4824
5051
|
* Checks if an entity action is currently enabled based on selection requirements
|
|
4825
5052
|
*/
|
|
4826
|
-
|
|
5053
|
+
IsEntityActionEnabled(action) {
|
|
4827
5054
|
if (!action.requiresSelection)
|
|
4828
5055
|
return true;
|
|
4829
5056
|
const selectedCount = this._selectedKeys.length;
|
|
@@ -4835,14 +5062,18 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4835
5062
|
return false;
|
|
4836
5063
|
return true;
|
|
4837
5064
|
}
|
|
5065
|
+
/** @deprecated Use {@link IsEntityActionEnabled}. */
|
|
5066
|
+
isEntityActionEnabled(action) {
|
|
5067
|
+
return this.IsEntityActionEnabled(action);
|
|
5068
|
+
}
|
|
4838
5069
|
// ========================================
|
|
4839
5070
|
// Overflow Menu Methods
|
|
4840
5071
|
// ========================================
|
|
4841
|
-
|
|
4842
|
-
this.
|
|
5072
|
+
ToggleOverflowMenu() {
|
|
5073
|
+
this.ShowOverflowMenu = !this.ShowOverflowMenu;
|
|
4843
5074
|
this.cdr.detectChanges();
|
|
4844
5075
|
// Add click outside listener when menu is open
|
|
4845
|
-
if (this.
|
|
5076
|
+
if (this.ShowOverflowMenu) {
|
|
4846
5077
|
setTimeout(() => {
|
|
4847
5078
|
document.addEventListener('click', this.handleOutsideClick);
|
|
4848
5079
|
}, 0);
|
|
@@ -4851,34 +5082,62 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4851
5082
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
4852
5083
|
}
|
|
4853
5084
|
}
|
|
4854
|
-
|
|
4855
|
-
|
|
5085
|
+
/** @deprecated Use {@link ToggleOverflowMenu}. */
|
|
5086
|
+
toggleOverflowMenu() {
|
|
5087
|
+
return this.ToggleOverflowMenu();
|
|
5088
|
+
}
|
|
5089
|
+
CloseOverflowMenu() {
|
|
5090
|
+
this.ShowOverflowMenu = false;
|
|
4856
5091
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
4857
5092
|
this.cdr.detectChanges();
|
|
4858
5093
|
}
|
|
5094
|
+
/** @deprecated Use {@link CloseOverflowMenu}. */
|
|
5095
|
+
closeOverflowMenu() {
|
|
5096
|
+
return this.CloseOverflowMenu();
|
|
5097
|
+
}
|
|
4859
5098
|
handleOutsideClick = () => {
|
|
4860
|
-
this.
|
|
5099
|
+
this.CloseOverflowMenu();
|
|
4861
5100
|
};
|
|
4862
5101
|
// Overflow menu visibility helpers
|
|
4863
|
-
get
|
|
4864
|
-
return this.
|
|
4865
|
-
this.
|
|
5102
|
+
get HasOverflowMenuItems() {
|
|
5103
|
+
return this.ShowExportInOverflow ||
|
|
5104
|
+
this.ShowColumnChooserInOverflow ||
|
|
4866
5105
|
(this._showEntityActionButtons && this._entityActions.length > 0) ||
|
|
4867
|
-
this.
|
|
5106
|
+
this.HasSelectionDependentOverflowActions;
|
|
4868
5107
|
}
|
|
4869
|
-
|
|
5108
|
+
/** @deprecated Use {@link HasOverflowMenuItems}. */
|
|
5109
|
+
get hasOverflowMenuItems() {
|
|
5110
|
+
return this.HasOverflowMenuItems;
|
|
5111
|
+
}
|
|
5112
|
+
get ShowExportInOverflow() {
|
|
4870
5113
|
// Export is in overflow when it's not shown as a primary button
|
|
4871
5114
|
return !this.ShowExportButton && !!this.ToolbarConfig.showExport;
|
|
4872
5115
|
}
|
|
4873
|
-
|
|
5116
|
+
/** @deprecated Use {@link ShowExportInOverflow}. */
|
|
5117
|
+
get showExportInOverflow() {
|
|
5118
|
+
return this.ShowExportInOverflow;
|
|
5119
|
+
}
|
|
5120
|
+
get ShowColumnChooserInOverflow() {
|
|
4874
5121
|
return this.AllowColumnToggle && !this.ToolbarConfig.showColumnChooser;
|
|
4875
5122
|
}
|
|
5123
|
+
/** @deprecated Use {@link ShowColumnChooserInOverflow}. */
|
|
5124
|
+
get showColumnChooserInOverflow() {
|
|
5125
|
+
return this.ShowColumnChooserInOverflow;
|
|
5126
|
+
}
|
|
5127
|
+
get HasSelectionDependentOverflowActions() {
|
|
5128
|
+
return this.ShowCommunicationInOverflow;
|
|
5129
|
+
}
|
|
5130
|
+
/** @deprecated Use {@link HasSelectionDependentOverflowActions}. */
|
|
4876
5131
|
get hasSelectionDependentOverflowActions() {
|
|
4877
|
-
return this.
|
|
5132
|
+
return this.HasSelectionDependentOverflowActions;
|
|
4878
5133
|
}
|
|
4879
|
-
get
|
|
5134
|
+
get ShowCommunicationInOverflow() {
|
|
4880
5135
|
// Communication is in overflow when it's not shown as a primary button
|
|
4881
|
-
return !this.ShowCommunicationButton && this.HasSelection;
|
|
5136
|
+
return !this.ShowCommunicationButton && this.HasSelection && this.EntitySupportsCommunication;
|
|
5137
|
+
}
|
|
5138
|
+
/** @deprecated Use {@link ShowCommunicationInOverflow}. */
|
|
5139
|
+
get showCommunicationInOverflow() {
|
|
5140
|
+
return this.ShowCommunicationInOverflow;
|
|
4882
5141
|
}
|
|
4883
5142
|
// ========================================
|
|
4884
5143
|
// Toolbar Button State Helpers
|
|
@@ -4892,13 +5151,13 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4892
5151
|
// ========================================
|
|
4893
5152
|
// CSS Helpers
|
|
4894
5153
|
// ========================================
|
|
4895
|
-
get
|
|
5154
|
+
get GridContainerClasses() {
|
|
4896
5155
|
const classes = ['mj-grid-container'];
|
|
4897
5156
|
classes.push(`grid-lines-${this._gridLines}`);
|
|
4898
5157
|
if (this._striped)
|
|
4899
5158
|
classes.push('grid-striped');
|
|
4900
5159
|
// Add visual config classes
|
|
4901
|
-
const vc = this.
|
|
5160
|
+
const vc = this.EffectiveVisualConfig;
|
|
4902
5161
|
classes.push(`header-style-${vc.headerStyle}`);
|
|
4903
5162
|
if (vc.headerShadow)
|
|
4904
5163
|
classes.push('header-shadow');
|
|
@@ -4911,11 +5170,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4911
5170
|
classes.push(`checkbox-style-${vc.checkboxStyle}`);
|
|
4912
5171
|
return classes;
|
|
4913
5172
|
}
|
|
5173
|
+
/** @deprecated Use {@link GridContainerClasses}. */
|
|
5174
|
+
get gridContainerClasses() {
|
|
5175
|
+
return this.GridContainerClasses;
|
|
5176
|
+
}
|
|
4914
5177
|
/**
|
|
4915
5178
|
* Apply visual configuration by setting CSS custom properties
|
|
4916
5179
|
*/
|
|
4917
5180
|
applyVisualConfig() {
|
|
4918
|
-
const vc = this.
|
|
5181
|
+
const vc = this.EffectiveVisualConfig;
|
|
4919
5182
|
const el = this.elementRef.nativeElement;
|
|
4920
5183
|
// Set CSS custom properties for dynamic values
|
|
4921
5184
|
if (vc.headerBackground) {
|
|
@@ -4951,16 +5214,20 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4951
5214
|
this.buildAgColumnDefs();
|
|
4952
5215
|
}
|
|
4953
5216
|
}
|
|
4954
|
-
get
|
|
5217
|
+
get GridHeightStyle() {
|
|
4955
5218
|
if (typeof this._height === 'number') {
|
|
4956
5219
|
return `${this._height}px`;
|
|
4957
5220
|
}
|
|
4958
5221
|
return this._height === 'auto' ? '100%' : 'fit-content';
|
|
4959
5222
|
}
|
|
5223
|
+
/** @deprecated Use {@link GridHeightStyle}. */
|
|
5224
|
+
get gridHeightStyle() {
|
|
5225
|
+
return this.GridHeightStyle;
|
|
5226
|
+
}
|
|
4960
5227
|
// ========================================
|
|
4961
5228
|
// Toolbar Button Helpers
|
|
4962
5229
|
// ========================================
|
|
4963
|
-
|
|
5230
|
+
IsButtonVisible(button) {
|
|
4964
5231
|
if (button.visible === undefined)
|
|
4965
5232
|
return true;
|
|
4966
5233
|
if (typeof button.visible === 'boolean')
|
|
@@ -4969,7 +5236,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4969
5236
|
return button.visible();
|
|
4970
5237
|
return true;
|
|
4971
5238
|
}
|
|
4972
|
-
|
|
5239
|
+
/** @deprecated Use {@link IsButtonVisible}. */
|
|
5240
|
+
isButtonVisible(button) {
|
|
5241
|
+
return this.IsButtonVisible(button);
|
|
5242
|
+
}
|
|
5243
|
+
IsButtonDisabled(button) {
|
|
4973
5244
|
if (button.disabled === undefined)
|
|
4974
5245
|
return false;
|
|
4975
5246
|
if (typeof button.disabled === 'boolean')
|
|
@@ -4978,11 +5249,19 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4978
5249
|
return button.disabled();
|
|
4979
5250
|
return false;
|
|
4980
5251
|
}
|
|
4981
|
-
|
|
5252
|
+
/** @deprecated Use {@link IsButtonDisabled}. */
|
|
5253
|
+
isButtonDisabled(button) {
|
|
5254
|
+
return this.IsButtonDisabled(button);
|
|
5255
|
+
}
|
|
5256
|
+
OnToolbarButtonClick(button) {
|
|
4982
5257
|
if (button.onClick) {
|
|
4983
5258
|
button.onClick();
|
|
4984
5259
|
}
|
|
4985
5260
|
}
|
|
5261
|
+
/** @deprecated Use {@link OnToolbarButtonClick}. */
|
|
5262
|
+
onToolbarButtonClick(button) {
|
|
5263
|
+
return this.OnToolbarButtonClick(button);
|
|
5264
|
+
}
|
|
4986
5265
|
// ========================================
|
|
4987
5266
|
// Public Getters
|
|
4988
5267
|
// ========================================
|
|
@@ -4999,15 +5278,15 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
4999
5278
|
i0.ɵɵviewQuery(_c0, 5);
|
|
5000
5279
|
} if (rf & 2) {
|
|
5001
5280
|
let _t;
|
|
5002
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.
|
|
5003
|
-
} }, 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) {
|
|
5281
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.GridContainer = _t.first);
|
|
5282
|
+
} }, 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) {
|
|
5004
5283
|
i0.ɵɵelementStart(0, "div", null, 0);
|
|
5005
5284
|
i0.ɵɵconditionalCreate(2, EntityDataGridComponent_Conditional_2_Template, 27, 19, "div", 1);
|
|
5006
5285
|
i0.ɵɵconditionalCreate(3, EntityDataGridComponent_Conditional_3_Template, 4, 1, "div", 2);
|
|
5007
5286
|
i0.ɵɵelementStart(4, "div", 3);
|
|
5008
5287
|
i0.ɵɵconditionalCreate(5, EntityDataGridComponent_Conditional_5_Template, 2, 0, "div", 4);
|
|
5009
5288
|
i0.ɵɵconditionalCreate(6, EntityDataGridComponent_Conditional_6_Template, 1, 2, "mj-empty-state", 5);
|
|
5010
|
-
i0.ɵɵconditionalCreate(7, EntityDataGridComponent_Conditional_7_Template, 1,
|
|
5289
|
+
i0.ɵɵconditionalCreate(7, EntityDataGridComponent_Conditional_7_Template, 1, 2, "mj-empty-state", 6);
|
|
5011
5290
|
i0.ɵɵconditionalCreate(8, EntityDataGridComponent_Conditional_8_Template, 1, 9, "ag-grid-angular", 7);
|
|
5012
5291
|
i0.ɵɵconditionalCreate(9, EntityDataGridComponent_Conditional_9_Template, 1, 13, "ag-grid-angular", 8);
|
|
5013
5292
|
i0.ɵɵelementEnd();
|
|
@@ -5067,7 +5346,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
5067
5346
|
animate('100ms ease-in', style({ opacity: 0, transform: 'translateY(-8px)' }))
|
|
5068
5347
|
])
|
|
5069
5348
|
])
|
|
5070
|
-
], 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) {\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"] }]
|
|
5349
|
+
], 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"] }]
|
|
5071
5350
|
}], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i1.ExportService }, { type: i0.NgZone }], { Params: [{
|
|
5072
5351
|
type: Input
|
|
5073
5352
|
}], AllowLoad: [{
|
|
@@ -5310,9 +5589,9 @@ export class EntityDataGridComponent extends BaseAngularComponent {
|
|
|
5310
5589
|
type: Output
|
|
5311
5590
|
}], EntityActionRequested: [{
|
|
5312
5591
|
type: Output
|
|
5313
|
-
}],
|
|
5592
|
+
}], GridContainer: [{
|
|
5314
5593
|
type: ViewChild,
|
|
5315
5594
|
args: ['gridContainer']
|
|
5316
5595
|
}] }); })();
|
|
5317
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber:
|
|
5596
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber: 195 }); })();
|
|
5318
5597
|
//# sourceMappingURL=entity-data-grid.component.js.map
|