@memberjunction/ng-entity-viewer 6.1.4 → 6.1.5

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.
Files changed (40) hide show
  1. package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts +18 -2
  2. package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts.map +1 -1
  3. package/dist/lib/aggregate-panel/aggregate-panel.component.js +47 -18
  4. package/dist/lib/aggregate-panel/aggregate-panel.component.js.map +1 -1
  5. package/dist/lib/entity-cards/entity-cards.component.d.ts +1 -0
  6. package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
  7. package/dist/lib/entity-cards/entity-cards.component.js +11 -2
  8. package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
  9. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +18 -4
  10. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
  11. package/dist/lib/entity-data-grid/entity-data-grid.component.js +55 -45
  12. package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
  13. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
  14. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +10 -8
  15. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
  16. package/dist/lib/utils/aggregate-field.util.d.ts +21 -0
  17. package/dist/lib/utils/aggregate-field.util.d.ts.map +1 -0
  18. package/dist/lib/utils/aggregate-field.util.js +40 -0
  19. package/dist/lib/utils/aggregate-field.util.js.map +1 -0
  20. package/dist/lib/utils/export-column.util.d.ts +14 -0
  21. package/dist/lib/utils/export-column.util.d.ts.map +1 -0
  22. package/dist/lib/utils/export-column.util.js +34 -0
  23. package/dist/lib/utils/export-column.util.js.map +1 -0
  24. package/dist/lib/view-config-panel/view-config-panel.component.d.ts +5 -1
  25. package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
  26. package/dist/lib/view-config-panel/view-config-panel.component.js +15 -8
  27. package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
  28. package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts +2 -0
  29. package/dist/lib/view-types/renderers/timeline-view-renderer.component.d.ts.map +1 -1
  30. package/dist/lib/view-types/renderers/timeline-view-renderer.component.js +11 -2
  31. package/dist/lib/view-types/renderers/timeline-view-renderer.component.js.map +1 -1
  32. package/dist/lib/view-workspace/view-workspace.component.d.ts +2 -1
  33. package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
  34. package/dist/lib/view-workspace/view-workspace.component.js +10 -4
  35. package/dist/lib/view-workspace/view-workspace.component.js.map +1 -1
  36. package/dist/public-api.d.ts +2 -0
  37. package/dist/public-api.d.ts.map +1 -1
  38. package/dist/public-api.js +2 -0
  39. package/dist/public-api.js.map +1 -1
  40. package/package.json +20 -20
@@ -3,11 +3,13 @@ import { trigger, transition, style, animate } from '@angular/animations';
3
3
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
4
4
  import { Subject } from 'rxjs';
5
5
  import { debounceTime, takeUntil } from 'rxjs/operators';
6
- import { LogError, RunView, RunViewParams, CoerceImageSrc, ParseCssHexColor, CompositeKey } from '@memberjunction/core';
6
+ import { ExportColumnTypeForSQLType } from '../utils/export-column.util';
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
10
  import { CommunicationEngineBase } from '@memberjunction/communication-types';
10
11
  import { buildPkString, canonicalizeColumnFields, computeFieldsList } from '../utils/record.util';
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';
13
15
  // Use the existing HighlightUtil from entity-viewer package
@@ -1594,11 +1596,33 @@ export class EntityDataGridComponent extends BaseAngularComponent {
1594
1596
  minimumFractionDigits: 0
1595
1597
  });
1596
1598
  }
1599
+ // A date aggregate reaches the browser as an ISO string: the server JSON-stringifies the value
1600
+ // and the client parses it back, which turns a Date into text. Resolve the column's type first
1601
+ // so a `date` column renders as its stored day whether the value is a Date or that string.
1602
+ if (typeof value !== 'boolean' && this.aggregateIsDateOnly(agg)) {
1603
+ return FormatDateOnly(value);
1604
+ }
1597
1605
  if (value instanceof Date) {
1598
1606
  return value.toLocaleDateString();
1599
1607
  }
1608
+ // A timestamp aggregate arrives as the same ISO string. It names an instant, so it is rendered
1609
+ // in local time, as a Date instance would be — not printed as the wire text.
1610
+ if (typeof value === 'string' && AggregateField(agg, this._entityInfo)?.TSType === EntityFieldTSType.Date) {
1611
+ const date = new Date(value);
1612
+ if (!isNaN(date.getTime()))
1613
+ return date.toLocaleDateString();
1614
+ }
1600
1615
  return String(value);
1601
1616
  }
1617
+ /**
1618
+ * Whether an aggregate summarises a date-only column. An aggregate carries no field metadata of
1619
+ * its own, so the column is read from a single-field expression such as `MIN(IntakeDate)` or
1620
+ * from the column the aggregate is pinned under. A `date` column is a calendar day and must not
1621
+ * be shifted into the reader's zone (MJ#4210).
1622
+ */
1623
+ aggregateIsDateOnly(agg) {
1624
+ return IsDateOnlySQLType(AggregateField(agg, this._entityInfo)?.Type);
1625
+ }
1602
1626
  // ========================================
1603
1627
  // Event Outputs
1604
1628
  // ========================================
@@ -3135,7 +3159,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3135
3159
  const useCustomFormat = customFormat && customFormat.type && customFormat.type !== 'auto';
3136
3160
  if (useCustomFormat) {
3137
3161
  // Use custom formatting
3138
- displayValue = this.formatValueWithCustomFormat(params.value, customFormat);
3162
+ displayValue = this.formatValueWithCustomFormat(params.value, customFormat, field);
3139
3163
  // Check if formatCustomBoolean returned HTML (icon or checkbox)
3140
3164
  if (customFormat.type === 'boolean' &&
3141
3165
  (customFormat.booleanDisplay === 'icon' || customFormat.booleanDisplay === 'checkbox')) {
@@ -3159,20 +3183,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3159
3183
  }
3160
3184
  // Date formatting
3161
3185
  else if (fieldType === 'Date') {
3162
- const date = params.value instanceof Date ? params.value : new Date(params.value);
3163
- if (isNaN(date.getTime())) {
3164
- displayValue = String(params.value);
3165
- }
3166
- else if (vc.friendlyDates) {
3167
- displayValue = date.toLocaleDateString(undefined, {
3168
- month: 'short',
3169
- day: 'numeric',
3170
- year: 'numeric'
3171
- });
3172
- }
3173
- else {
3174
- displayValue = date.toISOString().split('T')[0];
3175
- }
3186
+ displayValue = this.formatDefaultDate(params.value, field, !!vc.friendlyDates);
3176
3187
  }
3177
3188
  // Currency formatting
3178
3189
  else if (fieldType === 'number' && isCurrency) {
@@ -3290,7 +3301,21 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3290
3301
  /**
3291
3302
  * Format a value using custom ColumnFormat settings
3292
3303
  */
3293
- formatValueWithCustomFormat(value, format) {
3304
+ /**
3305
+ * Default rendering of a date-family cell. A `date` column is a calendar day that arrives as UTC
3306
+ * midnight; a local-zone formatter would land on the previous day for every reader west of
3307
+ * Greenwich (MJ#4210). A timestamp names an instant and stays in local time.
3308
+ */
3309
+ formatDefaultDate(value, field, friendlyDates) {
3310
+ const date = value instanceof Date ? value : new Date(value);
3311
+ if (isNaN(date.getTime()))
3312
+ return String(value);
3313
+ if (!friendlyDates)
3314
+ return date.toISOString().split('T')[0];
3315
+ const options = { month: 'short', day: 'numeric', year: 'numeric' };
3316
+ return IsDateOnlySQLType(field.Type) ? FormatDateOnly(date, options) : date.toLocaleDateString(undefined, options);
3317
+ }
3318
+ formatValueWithCustomFormat(value, format, field) {
3294
3319
  if (value == null)
3295
3320
  return '—';
3296
3321
  switch (format.type) {
@@ -3302,7 +3327,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3302
3327
  return this.formatCustomPercent(value, format);
3303
3328
  case 'date':
3304
3329
  case 'datetime':
3305
- return this.formatCustomDate(value, format);
3330
+ return this.formatCustomDate(value, format, IsDateOnlySQLType(field.Type));
3306
3331
  case 'boolean':
3307
3332
  return this.formatCustomBoolean(value, format);
3308
3333
  default:
@@ -3344,7 +3369,12 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3344
3369
  // Assume value is already a percentage (e.g., 50 = 50%), divide by 100
3345
3370
  return new Intl.NumberFormat('en-US', options).format(num / 100);
3346
3371
  }
3347
- formatCustomDate(value, format) {
3372
+ /**
3373
+ * @param dateOnly The column is a SQL `date`: a calendar day at UTC midnight with no time to show.
3374
+ * It is rendered in UTC so the day does not shift west of Greenwich, and a `datetime` column
3375
+ * format cannot add a time of day to it (MJ#4210). A timestamp column keeps local rendering.
3376
+ */
3377
+ formatCustomDate(value, format, dateOnly) {
3348
3378
  const date = value instanceof Date ? value : new Date(value);
3349
3379
  if (isNaN(date.getTime()))
3350
3380
  return String(value);
@@ -3352,6 +3382,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3352
3382
  const formatStr = format.dateFormat || 'medium';
3353
3383
  const includeWeekday = formatStr.includes('-weekday');
3354
3384
  const baseFormat = formatStr.replace('-weekday', '');
3385
+ const withTime = format.type === 'datetime' && !dateOnly;
3355
3386
  let options;
3356
3387
  // Intl.DateTimeFormat doesn't allow combining dateStyle with weekday
3357
3388
  // So we must use individual components when weekday is requested
@@ -3366,7 +3397,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3366
3397
  // medium
3367
3398
  options = { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' };
3368
3399
  }
3369
- if (format.type === 'datetime') {
3400
+ if (withTime) {
3370
3401
  options.hour = 'numeric';
3371
3402
  options.minute = '2-digit';
3372
3403
  }
@@ -3376,11 +3407,11 @@ export class EntityDataGridComponent extends BaseAngularComponent {
3376
3407
  options = {
3377
3408
  dateStyle: baseFormat === 'short' ? 'short' : baseFormat === 'long' ? 'long' : 'medium'
3378
3409
  };
3379
- if (format.type === 'datetime') {
3410
+ if (withTime) {
3380
3411
  options.timeStyle = 'short';
3381
3412
  }
3382
3413
  }
3383
- return new Intl.DateTimeFormat('en-US', options).format(date);
3414
+ return dateOnly ? FormatDateOnly(date, options, 'en-US') : new Intl.DateTimeFormat('en-US', options).format(date);
3384
3415
  }
3385
3416
  formatCustomBoolean(value, format) {
3386
3417
  if (format.booleanDisplay === 'icon') {
@@ -4625,7 +4656,7 @@ export class EntityDataGridComponent extends BaseAngularComponent {
4625
4656
  // exported a correctly-headed column of blank cells.
4626
4657
  name: field?.Name ?? colField,
4627
4658
  displayName: def.headerName || field?.DisplayNameOrName || colField,
4628
- dataType: this.mapFieldTypeToExportType(field?.Type)
4659
+ dataType: ExportColumnTypeForSQLType(field?.Type)
4629
4660
  };
4630
4661
  });
4631
4662
  }
@@ -4633,27 +4664,6 @@ export class EntityDataGridComponent extends BaseAngularComponent {
4633
4664
  isExportableColumn(def) {
4634
4665
  return !!def.field && def.field !== ROW_NUMBER_FIELD && def.colId !== FILLER_COLUMN_ID;
4635
4666
  }
4636
- /**
4637
- * Map MemberJunction field types to export column types
4638
- */
4639
- mapFieldTypeToExportType(fieldType) {
4640
- if (!fieldType)
4641
- return 'string';
4642
- const type = fieldType.toLowerCase();
4643
- if (type.includes('int') || type.includes('decimal') || type.includes('float') || type.includes('numeric')) {
4644
- return 'number';
4645
- }
4646
- if (type.includes('date') || type.includes('time')) {
4647
- return 'date';
4648
- }
4649
- if (type.includes('bit') || type.includes('bool')) {
4650
- return 'boolean';
4651
- }
4652
- if (type.includes('money') || type.includes('currency')) {
4653
- return 'currency';
4654
- }
4655
- return 'string';
4656
- }
4657
4667
  /**
4658
4668
  * Generate default file name for export
4659
4669
  */
@@ -5319,5 +5329,5 @@ export class EntityDataGridComponent extends BaseAngularComponent {
5319
5329
  type: ViewChild,
5320
5330
  args: ['gridContainer']
5321
5331
  }] }); })();
5322
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber: 193 }); })();
5332
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EntityDataGridComponent, { className: "EntityDataGridComponent", filePath: "src/lib/entity-data-grid/entity-data-grid.component.ts", lineNumber: 195 }); })();
5323
5333
  //# sourceMappingURL=entity-data-grid.component.js.map