@memberjunction/ng-record-changes 0.9.33 → 0.9.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,14 @@
1
1
  import { EventEmitter, OnInit, Renderer2, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';
2
2
  import { DomSanitizer } from '@angular/platform-browser';
3
3
  import { SortDescriptor } from '@progress/kendo-data-query';
4
+ import { BaseEntity } from '@memberjunction/core';
4
5
  import * as i0 from "@angular/core";
5
6
  export declare class RecordChangesComponent implements OnInit, AfterViewInit, OnDestroy {
6
7
  private sanitizer;
7
8
  private renderer;
8
9
  showloader: boolean;
9
10
  dialogClosed: EventEmitter<any>;
10
- record: any;
11
+ record: BaseEntity;
11
12
  wrapper: ElementRef;
12
13
  viewData: any;
13
14
  visibleColumns: any;
@@ -19,7 +20,13 @@ export declare class RecordChangesComponent implements OnInit, AfterViewInit, On
19
20
  LoadRecordChanges(recordId: number, appName: string, entityName: string): Promise<void>;
20
21
  prepareColumns(): void;
21
22
  closePropertiesDialog(): void;
22
- FormatColumnValue(col: any, value: any, maxLength?: number, trailingChars?: string): any;
23
+ FormatColumnValue(col: any, value: any, dataItem: any, maxLength?: number, trailingChars?: string): any;
24
+ protected fieldMarkup(fieldName: string): string;
25
+ protected valueMarkup(changeInfo: {
26
+ field: string;
27
+ oldValue: any;
28
+ newValue: any;
29
+ }, isOldValue: boolean): string;
23
30
  static ɵfac: i0.ɵɵFactoryDeclaration<RecordChangesComponent, never>;
24
31
  static ɵcmp: i0.ɵɵComponentDeclaration<RecordChangesComponent, "mj-record-changes", never, { "record": "record"; }, { "dialogClosed": "dialogClosed"; }, never, never, false, never>;
25
32
  }
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
11
- import { LogError, RunView } from '@memberjunction/core';
11
+ import { EntityFieldTSType, LogError, RunView } from '@memberjunction/core';
12
12
  import * as i0 from "@angular/core";
13
13
  import * as i1 from "@angular/platform-browser";
14
14
  import * as i2 from "@angular/common";
@@ -25,7 +25,7 @@ function RecordChangesComponent_div_5_kendo_grid_column_4_ng_template_1_Template
25
25
  const dataItem_r8 = ctx.$implicit;
26
26
  const item_r5 = i0.ɵɵnextContext().$implicit;
27
27
  const ctx_r7 = i0.ɵɵnextContext(2);
28
- i0.ɵɵproperty("innerHTML", ctx_r7.FormatColumnValue(item_r5, dataItem_r8[item_r5.field]), i0.ɵɵsanitizeHtml);
28
+ i0.ɵɵproperty("innerHTML", ctx_r7.FormatColumnValue(item_r5, dataItem_r8[item_r5.field], dataItem_r8), i0.ɵɵsanitizeHtml);
29
29
  } }
30
30
  const _c1 = function () { return { "font-weight": "bold", "background-color": "#a9c2af" }; };
31
31
  function RecordChangesComponent_div_5_kendo_grid_column_4_Template(rf, ctx) { if (rf & 1) {
@@ -55,7 +55,6 @@ export class RecordChangesComponent {
55
55
  this.renderer = renderer;
56
56
  this.showloader = false;
57
57
  this.dialogClosed = new EventEmitter();
58
- this.record = {};
59
58
  this.viewData = [];
60
59
  this.visibleColumns = [];
61
60
  this.sortSettings = [
@@ -102,9 +101,9 @@ export class RecordChangesComponent {
102
101
  prepareColumns() {
103
102
  this.visibleColumns = [{
104
103
  field: 'ChangedAt',
105
- title: 'Changed At (UTC)',
104
+ title: 'Date',
106
105
  type: 'datetime',
107
- width: 110,
106
+ width: 175,
108
107
  },
109
108
  {
110
109
  field: 'ChangesDescription',
@@ -115,37 +114,56 @@ export class RecordChangesComponent {
115
114
  closePropertiesDialog() {
116
115
  this.dialogClosed.emit();
117
116
  }
118
- FormatColumnValue(col, value, maxLength = 0, trailingChars = "...") {
117
+ FormatColumnValue(col, value, dataItem, maxLength = 0, trailingChars = "...") {
119
118
  if (value === null)
120
119
  return null;
121
120
  try {
122
121
  switch (col.type.toLowerCase()) {
123
122
  case 'datetime':
124
- let date = new Date(value);
123
+ let utcDate = new Date(value); // UTC date
124
+ // Calculate the local timezone offset and adjust the date
125
+ const localDate = new Date(utcDate.getTime() - utcDate.getTimezoneOffset() * 60000);
125
126
  return new Intl.DateTimeFormat('en-US', {
126
127
  year: 'numeric',
127
128
  month: 'short',
128
129
  day: 'numeric',
129
130
  hour: 'numeric',
130
131
  minute: 'numeric',
131
- second: 'numeric',
132
132
  hour12: true,
133
- }).format(date);
133
+ }).format(localDate);
134
134
  case 'int':
135
135
  return new Intl.NumberFormat(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value);
136
136
  case 'description':
137
- const regex = /([^ ]+) changed from ([^]+?) to ([^]+?)(?=\n|$)/g;
138
- let formattedDescription = '<ul>';
139
- let match;
140
- while ((match = regex.exec(value)) !== null) {
141
- const fieldName = match[1];
142
- const oldValue = match[2];
143
- const newValue = match[3];
144
- const formattedFieldChange = `<li>${fieldName} changed from <span style="color: darkgray;">${oldValue}</span> to <span style="color: blue;">${newValue}</span></li>`;
145
- formattedDescription += formattedFieldChange;
137
+ // while the database stores a description, it is easier to use the ChangesJSON field to get the structured data and then generate our HTML
138
+ const json = JSON.parse(dataItem.ChangesJSON);
139
+ if (!json)
140
+ return value;
141
+ else {
142
+ const fields = Object.keys(json); // each field that was changed is a key in the JSON object
143
+ let formattedDescription = '<div>';
144
+ for (let i = 0; i < fields.length; i++) {
145
+ const changeInfo = json[fields[i]];
146
+ const field = this.record.EntityInfo.Fields.find((f) => { var _a; return f.Name.trim().toLowerCase() === ((_a = changeInfo.field) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase()); });
147
+ let innerDescription = "";
148
+ if (field) {
149
+ // we have field metadata, so we can use that
150
+ if (field.TSType === EntityFieldTSType.Boolean) {
151
+ // for boolean fields, it is cleaner to just show new value, no need to show old value as it is always the opposite
152
+ innerDescription = `${this.fieldMarkup(field.DisplayNameOrName)} set to ${this.valueMarkup(changeInfo, false)}`;
153
+ }
154
+ else {
155
+ innerDescription = `${this.fieldMarkup(field.DisplayNameOrName)} changed from ${this.valueMarkup(changeInfo, true)} to ${this.valueMarkup(changeInfo, false)}`;
156
+ }
157
+ }
158
+ else {
159
+ // we don't have field metadata - this could happen if a field was removed or renamed after a record change was recorded in the database, so just use what we have
160
+ innerDescription = `${this.fieldMarkup(changeInfo.field)} changed from ${this.valueMarkup(changeInfo, true)}} to ${this.valueMarkup(changeInfo, false)}`;
161
+ }
162
+ formattedDescription += `<div>${innerDescription}</div>`;
163
+ }
164
+ formattedDescription += '</div>';
165
+ return this.sanitizer.bypassSecurityTrustHtml(formattedDescription);
146
166
  }
147
- formattedDescription += '</ul>';
148
- return this.sanitizer.bypassSecurityTrustHtml(formattedDescription);
149
167
  default:
150
168
  return value;
151
169
  }
@@ -155,6 +173,12 @@ export class RecordChangesComponent {
155
173
  return value;
156
174
  }
157
175
  }
176
+ fieldMarkup(fieldName) {
177
+ return `<b>${fieldName}</b>`;
178
+ }
179
+ valueMarkup(changeInfo, isOldValue) {
180
+ return `<span style="color: ${isOldValue ? 'darkgray' : 'blue'};">${isOldValue ? changeInfo.oldValue : changeInfo.newValue}</span>`;
181
+ }
158
182
  }
159
183
  RecordChangesComponent.ɵfac = function RecordChangesComponent_Factory(t) { return new (t || RecordChangesComponent)(i0.ɵɵdirectiveInject(i1.DomSanitizer), i0.ɵɵdirectiveInject(i0.Renderer2)); };
160
184
  RecordChangesComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RecordChangesComponent, selectors: [["mj-record-changes"]], viewQuery: function RecordChangesComponent_Query(rf, ctx) { if (rf & 1) {
@@ -180,7 +204,7 @@ RecordChangesComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: Reco
180
204
  } }, dependencies: [i2.NgForOf, i2.NgIf, i3.GridComponent, i3.DataBindingDirective, i3.ColumnComponent, i3.CellTemplateDirective, i4.WindowComponent, i5.LoaderComponent] });
181
205
  (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RecordChangesComponent, [{
182
206
  type: Component,
183
- args: [{ selector: 'mj-record-changes', template: "<div #recordChangesWrapper>\r\n <div class=\"k-overlay\"></div>\r\n <kendo-window \r\n class=\"kendo-window-custom\"\r\n [width]=\"700\"\r\n [height]=\"525\"\r\n [minHeight]=\"300\"\r\n [minWidth]=\"400\"\r\n [resizable]=\"true\"\r\n title=\"Record Changes\"\r\n (close)=\"closePropertiesDialog()\"\r\n >\r\n <kendo-loader *ngIf=\"showloader\" type=\"converging-spinner\" ></kendo-loader>\r\n <div class=\"k-d-flex k-flex-col k-justify-content-between k-h-full\" #dialogContainer *ngIf=\"!showloader\">\r\n <div class=\"kendo-grid-container\">\r\n <kendo-grid \r\n [kendoGridBinding]=\"viewData\" \r\n scrollable=\"virtual\" \r\n [pageSize]=\"100\" \r\n [sortable]=\"true\"\r\n [sort]=\"sortSettings\" \r\n [loading]=\"showloader\" \r\n [resizable]=\"true\" \r\n [navigable]=\"true\"\r\n >\r\n <kendo-grid-column \r\n *ngFor=\"let item of visibleColumns; let i = index\" \r\n field=\"{{item.field}}\"\r\n title=\"{{item.title}}\" \r\n [width]=\"item.width\"\r\n [headerStyle]=\"{ 'font-weight' : 'bold', 'background-color': '#a9c2af' }\"\r\n >\r\n <ng-template kendoGridCellTemplate let-dataItem>\r\n <span [innerHTML]=\"FormatColumnValue(item, dataItem[item.field])\"></span>\r\n </ng-template>\r\n </kendo-grid-column>\r\n </kendo-grid>\r\n </div>\r\n </div>\r\n </kendo-window>\r\n</div>" }]
207
+ args: [{ selector: 'mj-record-changes', template: "<div #recordChangesWrapper>\r\n <div class=\"k-overlay\"></div>\r\n <kendo-window \r\n class=\"kendo-window-custom\"\r\n [width]=\"700\"\r\n [height]=\"525\"\r\n [minHeight]=\"300\"\r\n [minWidth]=\"400\"\r\n [resizable]=\"true\"\r\n title=\"Record Changes\"\r\n (close)=\"closePropertiesDialog()\"\r\n >\r\n <kendo-loader *ngIf=\"showloader\" type=\"converging-spinner\" ></kendo-loader>\r\n <div class=\"k-d-flex k-flex-col k-justify-content-between k-h-full\" #dialogContainer *ngIf=\"!showloader\">\r\n <div class=\"kendo-grid-container\">\r\n <kendo-grid \r\n [kendoGridBinding]=\"viewData\" \r\n scrollable=\"virtual\" \r\n [pageSize]=\"100\" \r\n [sortable]=\"true\"\r\n [sort]=\"sortSettings\" \r\n [loading]=\"showloader\" \r\n [resizable]=\"true\" \r\n [navigable]=\"true\"\r\n >\r\n <kendo-grid-column \r\n *ngFor=\"let item of visibleColumns; let i = index\" \r\n field=\"{{item.field}}\"\r\n title=\"{{item.title}}\" \r\n [width]=\"item.width\"\r\n [headerStyle]=\"{ 'font-weight' : 'bold', 'background-color': '#a9c2af' }\"\r\n >\r\n <ng-template kendoGridCellTemplate let-dataItem>\r\n <span [innerHTML]=\"FormatColumnValue(item, dataItem[item.field], dataItem)\"></span>\r\n </ng-template>\r\n </kendo-grid-column>\r\n </kendo-grid>\r\n </div>\r\n </div>\r\n </kendo-window>\r\n</div>" }]
184
208
  }], function () { return [{ type: i1.DomSanitizer }, { type: i0.Renderer2 }]; }, { dialogClosed: [{
185
209
  type: Output
186
210
  }], record: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-record-changes",
3
- "version": "0.9.33",
3
+ "version": "0.9.36",
4
4
  "description": "MemberJunction: Angular pop-up window and grid to show changes made to a specific individual record",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -26,8 +26,8 @@
26
26
  "@progress/kendo-angular-grid": "^12.1.0"
27
27
  },
28
28
  "dependencies": {
29
- "@memberjunction/core": "^0.9.95",
30
- "@memberjunction/global": "^0.9.97",
29
+ "@memberjunction/core": "^0.9.99",
30
+ "@memberjunction/global": "^0.9.101",
31
31
  "@memberjunction/ng-compare-records": "^0.9.69",
32
32
  "@memberjunction/ng-container-directives": "^0.9.45",
33
33
  "tslib": "^2.3.0"