@memberjunction/ng-entity-viewer 6.1.2 → 6.2.0-edge.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts +18 -2
- package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts.map +1 -1
- package/dist/lib/aggregate-panel/aggregate-panel.component.js +47 -18
- package/dist/lib/aggregate-panel/aggregate-panel.component.js.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.d.ts +1 -0
- package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
- package/dist/lib/entity-cards/entity-cards.component.js +11 -2
- package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
- package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +18 -0
- 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 +71 -25
- package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
- 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 +10 -8
- package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts +11 -0
- package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
- package/dist/lib/entity-viewer/entity-viewer.component.js +29 -0
- package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
- package/dist/lib/utils/aggregate-field.util.d.ts +21 -0
- package/dist/lib/utils/aggregate-field.util.d.ts.map +1 -0
- package/dist/lib/utils/aggregate-field.util.js +40 -0
- package/dist/lib/utils/aggregate-field.util.js.map +1 -0
- package/dist/lib/view-config-panel/view-config-panel.component.d.ts +5 -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 +15 -8
- package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
- package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +93 -3
- 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 +432 -10
- package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
- package/dist/module.d.ts +5 -4
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +8 -1
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +1 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +1 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +20 -19
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter, OnInit } from '@angular/core';
|
|
2
2
|
import { MJUserViewEntity_IGridAggregate as ViewGridAggregate } from '@memberjunction/core-entities';
|
|
3
|
-
import { AggregateValue } from '@memberjunction/core';
|
|
3
|
+
import { AggregateValue, EntityInfo } from '@memberjunction/core';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
5
|
/**
|
|
6
6
|
* AggregatePanelComponent displays aggregate values in a card-based panel.
|
|
@@ -18,6 +18,13 @@ export declare class AggregatePanelComponent implements OnInit {
|
|
|
18
18
|
* Array of aggregate configurations to display
|
|
19
19
|
*/
|
|
20
20
|
Aggregates: ViewGridAggregate[];
|
|
21
|
+
/**
|
|
22
|
+
* The entity the aggregates are computed over. Optional: with it, the panel can read the SQL
|
|
23
|
+
* type of the column an aggregate summarises (`MIN(IntakeDate)`), so a `date` column renders as
|
|
24
|
+
* its stored calendar day and a timestamp in the reader's local time (MJ#4210). Without it, a
|
|
25
|
+
* date aggregate prints exactly as it arrives, which over GraphQL is an ISO string.
|
|
26
|
+
*/
|
|
27
|
+
Entity: EntityInfo | null;
|
|
21
28
|
/**
|
|
22
29
|
* Map of aggregate values, keyed by expression or id
|
|
23
30
|
*/
|
|
@@ -81,6 +88,13 @@ export declare class AggregatePanelComponent implements OnInit {
|
|
|
81
88
|
* Get the formatted value for an aggregate
|
|
82
89
|
*/
|
|
83
90
|
FormatValue(agg: ViewGridAggregate): string;
|
|
91
|
+
/**
|
|
92
|
+
* A date-family aggregate whose column type is known. The value may be a Date or, over
|
|
93
|
+
* GraphQL, the ISO string the server serialised it to. A `date` column is a calendar day at UTC
|
|
94
|
+
* midnight and is rendered in UTC so the day does not shift west of Greenwich; a timestamp is an
|
|
95
|
+
* instant and is rendered in local time.
|
|
96
|
+
*/
|
|
97
|
+
private formatDateField;
|
|
84
98
|
/**
|
|
85
99
|
* Get the CSS class for conditional styling
|
|
86
100
|
*/
|
|
@@ -113,11 +127,13 @@ export declare class AggregatePanelComponent implements OnInit {
|
|
|
113
127
|
* Format a date value
|
|
114
128
|
*/
|
|
115
129
|
private formatDate;
|
|
130
|
+
/** The Intl options for a simple date format preset. */
|
|
131
|
+
private dateOptions;
|
|
116
132
|
/**
|
|
117
133
|
* Evaluate a conditional styling rule
|
|
118
134
|
*/
|
|
119
135
|
private evaluateCondition;
|
|
120
136
|
static ɵfac: i0.ɵɵFactoryDeclaration<AggregatePanelComponent, never>;
|
|
121
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<AggregatePanelComponent, "mj-aggregate-panel", never, { "Aggregates": { "alias": "Aggregates"; "required": false; }; "Values": { "alias": "Values"; "required": false; }; "Position": { "alias": "Position"; "required": false; }; "Width": { "alias": "Width"; "required": false; }; "Layout": { "alias": "Layout"; "required": false; }; "GridColumns": { "alias": "GridColumns"; "required": false; }; "Title": { "alias": "Title"; "required": false; }; "Collapsible": { "alias": "Collapsible"; "required": false; }; "StartCollapsed": { "alias": "StartCollapsed"; "required": false; }; "Loading": { "alias": "Loading"; "required": false; }; }, { "CollapsedChange": "CollapsedChange"; "AggregateClick": "AggregateClick"; }, never, never, false, never>;
|
|
137
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<AggregatePanelComponent, "mj-aggregate-panel", never, { "Aggregates": { "alias": "Aggregates"; "required": false; }; "Entity": { "alias": "Entity"; "required": false; }; "Values": { "alias": "Values"; "required": false; }; "Position": { "alias": "Position"; "required": false; }; "Width": { "alias": "Width"; "required": false; }; "Layout": { "alias": "Layout"; "required": false; }; "GridColumns": { "alias": "GridColumns"; "required": false; }; "Title": { "alias": "Title"; "required": false; }; "Collapsible": { "alias": "Collapsible"; "required": false; }; "StartCollapsed": { "alias": "StartCollapsed"; "required": false; }; "Loading": { "alias": "Loading"; "required": false; }; }, { "CollapsedChange": "CollapsedChange"; "AggregateClick": "AggregateClick"; }, never, never, false, never>;
|
|
122
138
|
}
|
|
123
139
|
//# sourceMappingURL=aggregate-panel.component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate-panel.component.d.ts","sourceRoot":"","sources":["../../../src/lib/aggregate-panel/aggregate-panel.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,YAAY,EACZ,MAAM,EACP,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,+BAA+B,IAAI,iBAAiB,EAGrD,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"aggregate-panel.component.d.ts","sourceRoot":"","sources":["../../../src/lib/aggregate-panel/aggregate-panel.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,YAAY,EACZ,MAAM,EACP,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,+BAA+B,IAAI,iBAAiB,EAGrD,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,cAAc,EAAqB,UAAU,EAAqC,MAAM,sBAAsB,CAAC;;AAGxH;;;;;;;;;;GAUG;AACH,qBAMa,uBAAwB,YAAW,MAAM;IACpD;;OAEG;IACM,UAAU,EAAE,iBAAiB,EAAE,CAAM;IAE9C;;;;;OAKG;IACM,MAAM,EAAE,UAAU,GAAG,IAAI,CAAQ;IAE1C;;OAEG;IACM,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAa;IAEzD;;OAEG;IACM,QAAQ,EAAE,OAAO,GAAG,QAAQ,CAAW;IAEhD;;OAEG;IACM,KAAK,EAAE,MAAM,CAAO;IAE7B;;OAEG;IACM,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,MAAM,CAAc;IAEjE;;OAEG;IACM,WAAW,EAAE,MAAM,CAAK;IAEjC;;OAEG;IACM,KAAK,EAAE,MAAM,CAAa;IAEnC;;OAEG;IACM,WAAW,EAAE,OAAO,CAAQ;IAErC;;OAEG;IACM,cAAc,EAAE,OAAO,CAAS;IAEzC;;OAEG;IACM,OAAO,EAAE,OAAO,CAAS;IAElC;;OAEG;IACO,eAAe,wBAA+B;IAExD;;OAEG;IACO,cAAc,kCAAyC;IAEjE,WAAW,UAAS;IAEpB,QAAQ,IAAI,IAAI;IAIhB;;OAEG;IACH,cAAc,IAAI,IAAI;IAOtB;;;OAGG;IACH,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAK9C;;OAEG;IACH,gBAAgB,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI;IAI9C;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM;IAgB3C;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IASvB;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM;IAc7C;;OAEG;IACH,IAAI,iBAAiB,IAAI,iBAAiB,EAAE,CAE3C;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,iBAAiB,EAAE,CAIxC;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQvC;IAED;;OAEG;IACH,IAAI,mBAAmB,IAAI,MAAM,CAKhC;IAED;;OAEG;IACH,OAAO,CAAC,YAAY;IAyBpB;;OAEG;IACH,OAAO,CAAC,YAAY;IAoBpB;;OAEG;IACH,OAAO,CAAC,UAAU;IAQlB,wDAAwD;IACxD,OAAO,CAAC,WAAW;IAYnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;yCA7Qd,uBAAuB;2CAAvB,uBAAuB;CAmSnC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
import { EntityFieldTSType, FormatDateOnly, IsDateOnlySQLType } from '@memberjunction/core';
|
|
3
|
+
import { AggregateField } from '../utils/aggregate-field.util';
|
|
2
4
|
import * as i0 from "@angular/core";
|
|
3
5
|
import * as i1 from "@angular/common";
|
|
4
6
|
import * as i2 from "@memberjunction/ng-ui-components";
|
|
@@ -93,6 +95,13 @@ export class AggregatePanelComponent {
|
|
|
93
95
|
* Array of aggregate configurations to display
|
|
94
96
|
*/
|
|
95
97
|
Aggregates = [];
|
|
98
|
+
/**
|
|
99
|
+
* The entity the aggregates are computed over. Optional: with it, the panel can read the SQL
|
|
100
|
+
* type of the column an aggregate summarises (`MIN(IntakeDate)`), so a `date` column renders as
|
|
101
|
+
* its stored calendar day and a timestamp in the reader's local time (MJ#4210). Without it, a
|
|
102
|
+
* date aggregate prints exactly as it arrives, which over GraphQL is an ISO string.
|
|
103
|
+
*/
|
|
104
|
+
Entity = null;
|
|
96
105
|
/**
|
|
97
106
|
* Map of aggregate values, keyed by expression or id
|
|
98
107
|
*/
|
|
@@ -173,8 +182,29 @@ export class AggregatePanelComponent {
|
|
|
173
182
|
if (value == null)
|
|
174
183
|
return '—';
|
|
175
184
|
const format = agg.format || {};
|
|
185
|
+
// Only a value that is itself a date takes the date path. `COUNT(IntakeDate)` also resolves
|
|
186
|
+
// to the date column, but its value is the count — a number — and must format as one.
|
|
187
|
+
const field = AggregateField(agg, this.Entity);
|
|
188
|
+
if (field?.TSType === EntityFieldTSType.Date && (value instanceof Date || typeof value === 'string')) {
|
|
189
|
+
return this.formatDateField(value, field.Type, format);
|
|
190
|
+
}
|
|
176
191
|
return this.formatByType(value, format);
|
|
177
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* A date-family aggregate whose column type is known. The value may be a Date or, over
|
|
195
|
+
* GraphQL, the ISO string the server serialised it to. A `date` column is a calendar day at UTC
|
|
196
|
+
* midnight and is rendered in UTC so the day does not shift west of Greenwich; a timestamp is an
|
|
197
|
+
* instant and is rendered in local time.
|
|
198
|
+
*/
|
|
199
|
+
formatDateField(value, sqlType, format) {
|
|
200
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
201
|
+
if (isNaN(date.getTime()))
|
|
202
|
+
return String(value);
|
|
203
|
+
if (IsDateOnlySQLType(sqlType)) {
|
|
204
|
+
return FormatDateOnly(date, this.dateOptions(format.dateFormat), 'en-US');
|
|
205
|
+
}
|
|
206
|
+
return this.formatDate(date, format.dateFormat);
|
|
207
|
+
}
|
|
178
208
|
/**
|
|
179
209
|
* Get the CSS class for conditional styling
|
|
180
210
|
*/
|
|
@@ -274,27 +304,24 @@ export class AggregatePanelComponent {
|
|
|
274
304
|
*/
|
|
275
305
|
formatDate(value, dateFormat) {
|
|
276
306
|
try {
|
|
277
|
-
|
|
278
|
-
const options = {};
|
|
279
|
-
switch (dateFormat) {
|
|
280
|
-
case 'short':
|
|
281
|
-
options.dateStyle = 'short';
|
|
282
|
-
break;
|
|
283
|
-
case 'medium':
|
|
284
|
-
options.dateStyle = 'medium';
|
|
285
|
-
break;
|
|
286
|
-
case 'long':
|
|
287
|
-
options.dateStyle = 'long';
|
|
288
|
-
break;
|
|
289
|
-
default:
|
|
290
|
-
options.dateStyle = 'medium';
|
|
291
|
-
}
|
|
292
|
-
return new Intl.DateTimeFormat('en-US', options).format(value);
|
|
307
|
+
return new Intl.DateTimeFormat('en-US', this.dateOptions(dateFormat)).format(value);
|
|
293
308
|
}
|
|
294
309
|
catch {
|
|
295
310
|
return String(value);
|
|
296
311
|
}
|
|
297
312
|
}
|
|
313
|
+
/** The Intl options for a simple date format preset. */
|
|
314
|
+
dateOptions(dateFormat) {
|
|
315
|
+
switch (dateFormat) {
|
|
316
|
+
case 'short':
|
|
317
|
+
return { dateStyle: 'short' };
|
|
318
|
+
case 'long':
|
|
319
|
+
return { dateStyle: 'long' };
|
|
320
|
+
case 'medium':
|
|
321
|
+
default:
|
|
322
|
+
return { dateStyle: 'medium' };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
298
325
|
/**
|
|
299
326
|
* Evaluate a conditional styling rule
|
|
300
327
|
*/
|
|
@@ -321,7 +348,7 @@ export class AggregatePanelComponent {
|
|
|
321
348
|
}
|
|
322
349
|
}
|
|
323
350
|
static ɵfac = function AggregatePanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AggregatePanelComponent)(); };
|
|
324
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AggregatePanelComponent, selectors: [["mj-aggregate-panel"]], inputs: { Aggregates: "Aggregates", Values: "Values", Position: "Position", Width: "Width", Layout: "Layout", GridColumns: "GridColumns", Title: "Title", Collapsible: "Collapsible", StartCollapsed: "StartCollapsed", Loading: "Loading" }, outputs: { CollapsedChange: "CollapsedChange", AggregateClick: "AggregateClick" }, standalone: false, decls: 5, vars: 8, consts: [[1, "aggregate-panel", 3, "ngStyle"], ["Size", "sm", 3, "ExpandedChange", "FlushBody", "Expanded", "Disabled"], ["mjAccordionTitle", ""], ["mjAccordionActions", ""], ["mjAccordionBody", ""], [1, "panel-title"], [1, "loading-indicator"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "panel-content"], ["Size", "compact", "Icon", "fa-solid fa-chart-simple", "Title", "No summaries configured"], [1, "cards-container", 3, "ngStyle"], [1, "aggregate-card", 3, "class", "title"], [1, "aggregate-card", 3, "click", "title"], [1, "card-icon"], [1, "card-content"], [1, "card-value"], [1, "card-label"]], template: function AggregatePanelComponent_Template(rf, ctx) { if (rf & 1) {
|
|
351
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AggregatePanelComponent, selectors: [["mj-aggregate-panel"]], inputs: { Aggregates: "Aggregates", Entity: "Entity", Values: "Values", Position: "Position", Width: "Width", Layout: "Layout", GridColumns: "GridColumns", Title: "Title", Collapsible: "Collapsible", StartCollapsed: "StartCollapsed", Loading: "Loading" }, outputs: { CollapsedChange: "CollapsedChange", AggregateClick: "AggregateClick" }, standalone: false, decls: 5, vars: 8, consts: [[1, "aggregate-panel", 3, "ngStyle"], ["Size", "sm", 3, "ExpandedChange", "FlushBody", "Expanded", "Disabled"], ["mjAccordionTitle", ""], ["mjAccordionActions", ""], ["mjAccordionBody", ""], [1, "panel-title"], [1, "loading-indicator"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "panel-content"], ["Size", "compact", "Icon", "fa-solid fa-chart-simple", "Title", "No summaries configured"], [1, "cards-container", 3, "ngStyle"], [1, "aggregate-card", 3, "class", "title"], [1, "aggregate-card", 3, "click", "title"], [1, "card-icon"], [1, "card-content"], [1, "card-value"], [1, "card-label"]], template: function AggregatePanelComponent_Template(rf, ctx) { if (rf & 1) {
|
|
325
352
|
i0.ɵɵelementStart(0, "div", 0)(1, "mj-accordion-panel", 1);
|
|
326
353
|
i0.ɵɵlistener("ExpandedChange", function AggregatePanelComponent_Template_mj_accordion_panel_ExpandedChange_1_listener($event) { return ctx.OnPanelExpandedChange($event); });
|
|
327
354
|
i0.ɵɵtemplate(2, AggregatePanelComponent_ng_template_2_Template, 2, 1, "ng-template", 2)(3, AggregatePanelComponent_ng_template_3_Template, 1, 1, "ng-template", 3)(4, AggregatePanelComponent_ng_template_4_Template, 5, 11, "ng-template", 4);
|
|
@@ -338,6 +365,8 @@ export class AggregatePanelComponent {
|
|
|
338
365
|
args: [{ standalone: false, selector: 'mj-aggregate-panel', template: "<div class=\"aggregate-panel\"\n [class.position-right]=\"Position === 'right'\"\n [class.position-bottom]=\"Position === 'bottom'\"\n [ngStyle]=\"PanelStyle\">\n\n <!-- Migrated to <mj-accordion-panel>. The bespoke .panel-header / .collapse-toggle\n and the @if(!IsCollapsed) body gate are replaced by the accordion's own header,\n chevron, and lazy/animated body. Expanded is the inverse of IsCollapsed; the\n (ExpandedChange) handler preserves the CollapsedChange emit side-effect. -->\n <mj-accordion-panel\n Size=\"sm\"\n [FlushBody]=\"true\"\n [Expanded]=\"!IsCollapsed\"\n [Disabled]=\"!Collapsible\"\n (ExpandedChange)=\"OnPanelExpandedChange($event)\">\n\n <ng-template mjAccordionTitle>\n <span class=\"panel-title\">{{ Title }}</span>\n </ng-template>\n\n <!-- Loading indicator lives in the header actions slot (sibling of the chevron). -->\n <ng-template mjAccordionActions>\n @if (Loading) {\n <span class=\"loading-indicator\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </span>\n }\n </ng-template>\n\n <ng-template mjAccordionBody>\n <div class=\"panel-content\">\n <!-- Empty state -->\n @if (CardAggregates.length === 0) {\n <mj-empty-state\n Size=\"compact\"\n Icon=\"fa-solid fa-chart-simple\"\n Title=\"No summaries configured\" />\n }\n <!-- Cards Container -->\n <div class=\"cards-container\"\n [class.layout-horizontal]=\"Layout === 'horizontal'\"\n [class.layout-vertical]=\"Layout === 'vertical'\"\n [class.layout-grid]=\"Layout === 'grid'\"\n [ngStyle]=\"Layout === 'grid' ? {'grid-template-columns': GridTemplateColumns} : {}\">\n <!-- Individual Aggregate Cards -->\n @for (agg of CardAggregates; track agg) {\n <div class=\"aggregate-card\"\n [class]=\"GetStyleClass(agg)\"\n (click)=\"OnAggregateClick(agg)\"\n [title]=\"agg.description || agg.label\">\n <!-- Icon -->\n @if (agg.icon) {\n <div class=\"card-icon\">\n <i [class]=\"agg.icon\"></i>\n </div>\n }\n <!-- Content -->\n <div class=\"card-content\">\n <div class=\"card-value\" [class.loading]=\"Loading\">\n {{ Loading ? '...' : FormatValue(agg) }}\n </div>\n <div class=\"card-label\">{{ agg.label }}</div>\n </div>\n </div>\n }\n </div>\n </div>\n </ng-template>\n </mj-accordion-panel>\n</div>\n", styles: ["/* Aggregate Panel Component Styles */\n\n/* Layout/positioning only \u2014 visual chrome (border, radius, shadow, header,\n chevron, and .collapsed body-hiding) is now owned by <mj-accordion-panel>. */\n.aggregate-panel {\n display: flex;\n flex-direction: column;\n}\n\n/* Position variants */\n.position-right {\n height: 100%;\n}\n\n.position-bottom {\n width: 100%;\n}\n\n/* Title (projected into the accordion title slot) */\n.panel-title {\n font-weight: 600;\n font-size: 14px;\n color: var(--mj-text-primary);\n}\n\n/* Loading indicator (projected into the accordion actions slot) */\n.loading-indicator {\n color: var(--mj-brand-primary);\n font-size: 14px;\n}\n\n/* Panel Content */\n.panel-content {\n padding: 16px;\n overflow-y: auto;\n flex: 1;\n}\n\n/* Cards Container */\n.cards-container {\n display: flex;\n gap: 12px;\n}\n\n.layout-horizontal {\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n.layout-vertical {\n flex-direction: column;\n}\n\n.layout-grid {\n display: grid;\n gap: 12px;\n}\n\n/* Aggregate Card */\n.aggregate-card {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n padding: 12px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n cursor: pointer;\n transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;\n}\n\n.aggregate-card:hover {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n transform: translateY(-1px);\n}\n\n/* Card Icon */\n.card-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--mj-bg-surface-card);\n border-radius: 8px;\n color: var(--mj-brand-primary);\n font-size: 18px;\n flex-shrink: 0;\n}\n\n/* Card Content */\n.card-content {\n flex: 1;\n min-width: 0;\n}\n\n.card-value {\n font-size: 20px;\n font-weight: 700;\n color: var(--mj-text-primary);\n line-height: 1.2;\n margin-bottom: 4px;\n word-break: break-word;\n}\n\n.card-value.loading {\n color: var(--mj-text-disabled);\n}\n\n.card-label {\n font-size: 12px;\n color: var(--mj-text-secondary);\n line-height: 1.3;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Conditional Style Classes */\n.style-success {\n border-left: 4px solid var(--mj-status-success);\n}\n\n.style-success .card-value {\n color: var(--mj-status-success);\n}\n\n.style-success .card-icon {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success);\n}\n\n.style-warning {\n border-left: 4px solid var(--mj-status-warning);\n}\n\n.style-warning .card-value {\n color: var(--mj-status-warning-text);\n}\n\n.style-warning .card-icon {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n}\n\n.style-danger {\n border-left: 4px solid var(--mj-status-error);\n}\n\n.style-danger .card-value {\n color: var(--mj-status-error);\n}\n\n.style-danger .card-icon {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n}\n\n.style-info {\n border-left: 4px solid var(--mj-brand-primary);\n}\n\n.style-info .card-value {\n color: var(--mj-brand-primary);\n}\n\n.style-info .card-icon {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.style-muted {\n border-left: 4px solid var(--mj-text-muted);\n}\n\n.style-muted .card-value {\n color: var(--mj-text-muted);\n}\n\n.style-muted .card-icon {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n/* Responsive adjustments for bottom position */\n.position-bottom .cards-container.layout-vertical {\n flex-direction: row;\n flex-wrap: wrap;\n}\n\n.position-bottom .cards-container.layout-vertical .aggregate-card {\n flex: 1 1 200px;\n max-width: 300px;\n}\n"] }]
|
|
339
366
|
}], null, { Aggregates: [{
|
|
340
367
|
type: Input
|
|
368
|
+
}], Entity: [{
|
|
369
|
+
type: Input
|
|
341
370
|
}], Values: [{
|
|
342
371
|
type: Input
|
|
343
372
|
}], Position: [{
|
|
@@ -361,5 +390,5 @@ export class AggregatePanelComponent {
|
|
|
361
390
|
}], AggregateClick: [{
|
|
362
391
|
type: Output
|
|
363
392
|
}] }); })();
|
|
364
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AggregatePanelComponent, { className: "AggregatePanelComponent", filePath: "src/lib/aggregate-panel/aggregate-panel.component.ts", lineNumber:
|
|
393
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AggregatePanelComponent, { className: "AggregatePanelComponent", filePath: "src/lib/aggregate-panel/aggregate-panel.component.ts", lineNumber: 33 }); })();
|
|
365
394
|
//# sourceMappingURL=aggregate-panel.component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregate-panel.component.js","sourceRoot":"","sources":["../../../src/lib/aggregate-panel/aggregate-panel.component.ts","../../../src/lib/aggregate-panel/aggregate-panel.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n OnInit\n} from '@angular/core';\nimport {\n MJUserViewEntity_IGridAggregate as ViewGridAggregate,\n MJUserViewEntity_IAggregateValueFormat as AggregateValueFormat,\n MJUserViewEntity_IAggregateConditionalStyle as AggregateConditionalStyle\n} from '@memberjunction/core-entities';\nimport { AggregateValue } from '@memberjunction/core';\n\n/**\n * AggregatePanelComponent displays aggregate values in a card-based panel.\n * Can be positioned to the right or bottom of the grid, with various layout options.\n *\n * Features:\n * - Collapsible panel with customizable title\n * - Multiple layout options (horizontal, vertical, grid)\n * - Value formatting (number, currency, percent, date)\n * - Conditional styling (green/yellow/red based on value thresholds)\n * - Icon support for visual indicators\n */\n@Component({\n standalone: false,\n selector: 'mj-aggregate-panel',\n templateUrl: './aggregate-panel.component.html',\n styleUrls: ['./aggregate-panel.component.css']\n})\nexport class AggregatePanelComponent implements OnInit {\n /**\n * Array of aggregate configurations to display\n */\n @Input() Aggregates: ViewGridAggregate[] = [];\n\n /**\n * Map of aggregate values, keyed by expression or id\n */\n @Input() Values: Map<string, AggregateValue> = new Map();\n\n /**\n * Panel position relative to the grid\n */\n @Input() Position: 'right' | 'bottom' = 'right';\n\n /**\n * Panel width in pixels (only applies when Position is 'right')\n */\n @Input() Width: number = 280;\n\n /**\n * Layout style for aggregate cards\n */\n @Input() Layout: 'horizontal' | 'vertical' | 'grid' = 'vertical';\n\n /**\n * Number of columns when Layout is 'grid'\n */\n @Input() GridColumns: number = 2;\n\n /**\n * Panel title\n */\n @Input() Title: string = 'Summary';\n\n /**\n * Whether the panel can be collapsed\n */\n @Input() Collapsible: boolean = true;\n\n /**\n * Whether the panel starts in collapsed state\n */\n @Input() StartCollapsed: boolean = false;\n\n /**\n * Whether aggregates are currently loading\n */\n @Input() Loading: boolean = false;\n\n /**\n * Emitted when panel collapsed state changes\n */\n @Output() CollapsedChange = new EventEmitter<boolean>();\n\n /**\n * Emitted when user clicks an aggregate card (for editing/details)\n */\n @Output() AggregateClick = new EventEmitter<ViewGridAggregate>();\n\n IsCollapsed = false;\n\n ngOnInit(): void {\n this.IsCollapsed = this.StartCollapsed;\n }\n\n /**\n * Toggle panel collapsed state\n */\n ToggleCollapse(): void {\n if (this.Collapsible) {\n this.IsCollapsed = !this.IsCollapsed;\n this.CollapsedChange.emit(this.IsCollapsed);\n }\n }\n\n /**\n * Handle the accordion panel's ExpandedChange. Accordion `Expanded` is the\n * inverse of our `IsCollapsed`; preserves the CollapsedChange emit side-effect.\n */\n OnPanelExpandedChange(expanded: boolean): void {\n this.IsCollapsed = !expanded;\n this.CollapsedChange.emit(this.IsCollapsed);\n }\n\n /**\n * Handle click on an aggregate card\n */\n OnAggregateClick(agg: ViewGridAggregate): void {\n this.AggregateClick.emit(agg);\n }\n\n /**\n * Get the formatted value for an aggregate\n */\n FormatValue(agg: ViewGridAggregate): string {\n const key = agg.id || agg.expression;\n const value = this.Values.get(key);\n\n if (value == null) return '—';\n\n const format = agg.format || {};\n return this.formatByType(value, format);\n }\n\n /**\n * Get the CSS class for conditional styling\n */\n GetStyleClass(agg: ViewGridAggregate): string {\n const key = agg.id || agg.expression;\n const value = this.Values.get(key);\n\n if (value == null || !agg.conditionalStyles?.length) return '';\n\n for (const rule of agg.conditionalStyles) {\n if (this.evaluateCondition(value, rule)) {\n return `style-${rule.style}`;\n }\n }\n return '';\n }\n\n /**\n * Get enabled aggregates (filter out disabled ones)\n */\n get EnabledAggregates(): ViewGridAggregate[] {\n return this.Aggregates.filter(a => a.enabled !== false);\n }\n\n /**\n * Get card aggregates sorted by order\n */\n get CardAggregates(): ViewGridAggregate[] {\n return this.EnabledAggregates\n .filter(a => a.displayType === 'card')\n .sort((a, b) => (a.order || 0) - (b.order || 0));\n }\n\n /**\n * Get panel style for right position\n */\n get PanelStyle(): Record<string, string> {\n if (this.Position === 'right') {\n return {\n width: `${this.Width}px`,\n minWidth: `${this.Width}px`\n };\n }\n return {};\n }\n\n /**\n * Get grid template columns for grid layout\n */\n get GridTemplateColumns(): string {\n if (this.Layout === 'grid') {\n return `repeat(${this.GridColumns}, 1fr)`;\n }\n return '';\n }\n\n /**\n * Format value based on type configuration\n */\n private formatByType(value: AggregateValue, format: AggregateValueFormat): string {\n if (value == null) return '—';\n\n // Handle prefix/suffix\n const prefix = format.prefix || '';\n const suffix = format.suffix || '';\n\n // Handle different value types\n if (typeof value === 'number') {\n const formatted = this.formatNumber(value, format);\n return `${prefix}${formatted}${suffix}`;\n }\n\n if (value instanceof Date) {\n return this.formatDate(value, format.dateFormat);\n }\n\n if (typeof value === 'boolean') {\n return value ? 'Yes' : 'No';\n }\n\n // String or unknown type\n return `${prefix}${String(value)}${suffix}`;\n }\n\n /**\n * Format a number value\n */\n private formatNumber(value: number, format: AggregateValueFormat): string {\n const options: Intl.NumberFormatOptions = {\n minimumFractionDigits: format.decimals ?? 0,\n maximumFractionDigits: format.decimals ?? 2,\n useGrouping: format.thousandsSeparator !== false\n };\n\n // Handle currency\n if (format.currencyCode) {\n options.style = 'currency';\n options.currency = format.currencyCode;\n }\n\n try {\n return new Intl.NumberFormat('en-US', options).format(value);\n } catch {\n return String(value);\n }\n }\n\n /**\n * Format a date value\n */\n private formatDate(value: Date, dateFormat?: string): string {\n try {\n // Simple format presets\n const options: Intl.DateTimeFormatOptions = {};\n\n switch (dateFormat) {\n case 'short':\n options.dateStyle = 'short';\n break;\n case 'medium':\n options.dateStyle = 'medium';\n break;\n case 'long':\n options.dateStyle = 'long';\n break;\n default:\n options.dateStyle = 'medium';\n }\n\n return new Intl.DateTimeFormat('en-US', options).format(value);\n } catch {\n return String(value);\n }\n }\n\n /**\n * Evaluate a conditional styling rule\n */\n private evaluateCondition(value: AggregateValue, rule: AggregateConditionalStyle): boolean {\n // For non-numeric values, only support equality checks\n if (typeof value !== 'number') {\n if (rule.operator === 'eq') return value === rule.value;\n if (rule.operator === 'neq') return value !== rule.value;\n return false;\n }\n\n const numValue = Number(rule.value);\n const numValue2 = rule.value2 != null ? Number(rule.value2) : numValue;\n\n switch (rule.operator) {\n case 'gt': return value > numValue;\n case 'gte': return value >= numValue;\n case 'lt': return value < numValue;\n case 'lte': return value <= numValue;\n case 'eq': return value === numValue;\n case 'neq': return value !== numValue;\n case 'between': return value >= numValue && value <= numValue2;\n default: return false;\n }\n }\n}\n","<div class=\"aggregate-panel\"\n [class.position-right]=\"Position === 'right'\"\n [class.position-bottom]=\"Position === 'bottom'\"\n [ngStyle]=\"PanelStyle\">\n\n <!-- Migrated to <mj-accordion-panel>. The bespoke .panel-header / .collapse-toggle\n and the @if(!IsCollapsed) body gate are replaced by the accordion's own header,\n chevron, and lazy/animated body. Expanded is the inverse of IsCollapsed; the\n (ExpandedChange) handler preserves the CollapsedChange emit side-effect. -->\n <mj-accordion-panel\n Size=\"sm\"\n [FlushBody]=\"true\"\n [Expanded]=\"!IsCollapsed\"\n [Disabled]=\"!Collapsible\"\n (ExpandedChange)=\"OnPanelExpandedChange($event)\">\n\n <ng-template mjAccordionTitle>\n <span class=\"panel-title\">{{ Title }}</span>\n </ng-template>\n\n <!-- Loading indicator lives in the header actions slot (sibling of the chevron). -->\n <ng-template mjAccordionActions>\n @if (Loading) {\n <span class=\"loading-indicator\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </span>\n }\n </ng-template>\n\n <ng-template mjAccordionBody>\n <div class=\"panel-content\">\n <!-- Empty state -->\n @if (CardAggregates.length === 0) {\n <mj-empty-state\n Size=\"compact\"\n Icon=\"fa-solid fa-chart-simple\"\n Title=\"No summaries configured\" />\n }\n <!-- Cards Container -->\n <div class=\"cards-container\"\n [class.layout-horizontal]=\"Layout === 'horizontal'\"\n [class.layout-vertical]=\"Layout === 'vertical'\"\n [class.layout-grid]=\"Layout === 'grid'\"\n [ngStyle]=\"Layout === 'grid' ? {'grid-template-columns': GridTemplateColumns} : {}\">\n <!-- Individual Aggregate Cards -->\n @for (agg of CardAggregates; track agg) {\n <div class=\"aggregate-card\"\n [class]=\"GetStyleClass(agg)\"\n (click)=\"OnAggregateClick(agg)\"\n [title]=\"agg.description || agg.label\">\n <!-- Icon -->\n @if (agg.icon) {\n <div class=\"card-icon\">\n <i [class]=\"agg.icon\"></i>\n </div>\n }\n <!-- Content -->\n <div class=\"card-content\">\n <div class=\"card-value\" [class.loading]=\"Loading\">\n {{ Loading ? '...' : FormatValue(agg) }}\n </div>\n <div class=\"card-label\">{{ agg.label }}</div>\n </div>\n </div>\n }\n </div>\n </div>\n </ng-template>\n </mj-accordion-panel>\n</div>\n"]}
|
|
1
|
+
{"version":3,"file":"aggregate-panel.component.js","sourceRoot":"","sources":["../../../src/lib/aggregate-panel/aggregate-panel.component.ts","../../../src/lib/aggregate-panel/aggregate-panel.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n Input,\n Output,\n EventEmitter,\n OnInit\n} from '@angular/core';\nimport {\n MJUserViewEntity_IGridAggregate as ViewGridAggregate,\n MJUserViewEntity_IAggregateValueFormat as AggregateValueFormat,\n MJUserViewEntity_IAggregateConditionalStyle as AggregateConditionalStyle\n} from '@memberjunction/core-entities';\nimport { AggregateValue, EntityFieldTSType, EntityInfo, FormatDateOnly, IsDateOnlySQLType } from '@memberjunction/core';\nimport { AggregateField } from '../utils/aggregate-field.util';\n\n/**\n * AggregatePanelComponent displays aggregate values in a card-based panel.\n * Can be positioned to the right or bottom of the grid, with various layout options.\n *\n * Features:\n * - Collapsible panel with customizable title\n * - Multiple layout options (horizontal, vertical, grid)\n * - Value formatting (number, currency, percent, date)\n * - Conditional styling (green/yellow/red based on value thresholds)\n * - Icon support for visual indicators\n */\n@Component({\n standalone: false,\n selector: 'mj-aggregate-panel',\n templateUrl: './aggregate-panel.component.html',\n styleUrls: ['./aggregate-panel.component.css']\n})\nexport class AggregatePanelComponent implements OnInit {\n /**\n * Array of aggregate configurations to display\n */\n @Input() Aggregates: ViewGridAggregate[] = [];\n\n /**\n * The entity the aggregates are computed over. Optional: with it, the panel can read the SQL\n * type of the column an aggregate summarises (`MIN(IntakeDate)`), so a `date` column renders as\n * its stored calendar day and a timestamp in the reader's local time (MJ#4210). Without it, a\n * date aggregate prints exactly as it arrives, which over GraphQL is an ISO string.\n */\n @Input() Entity: EntityInfo | null = null;\n\n /**\n * Map of aggregate values, keyed by expression or id\n */\n @Input() Values: Map<string, AggregateValue> = new Map();\n\n /**\n * Panel position relative to the grid\n */\n @Input() Position: 'right' | 'bottom' = 'right';\n\n /**\n * Panel width in pixels (only applies when Position is 'right')\n */\n @Input() Width: number = 280;\n\n /**\n * Layout style for aggregate cards\n */\n @Input() Layout: 'horizontal' | 'vertical' | 'grid' = 'vertical';\n\n /**\n * Number of columns when Layout is 'grid'\n */\n @Input() GridColumns: number = 2;\n\n /**\n * Panel title\n */\n @Input() Title: string = 'Summary';\n\n /**\n * Whether the panel can be collapsed\n */\n @Input() Collapsible: boolean = true;\n\n /**\n * Whether the panel starts in collapsed state\n */\n @Input() StartCollapsed: boolean = false;\n\n /**\n * Whether aggregates are currently loading\n */\n @Input() Loading: boolean = false;\n\n /**\n * Emitted when panel collapsed state changes\n */\n @Output() CollapsedChange = new EventEmitter<boolean>();\n\n /**\n * Emitted when user clicks an aggregate card (for editing/details)\n */\n @Output() AggregateClick = new EventEmitter<ViewGridAggregate>();\n\n IsCollapsed = false;\n\n ngOnInit(): void {\n this.IsCollapsed = this.StartCollapsed;\n }\n\n /**\n * Toggle panel collapsed state\n */\n ToggleCollapse(): void {\n if (this.Collapsible) {\n this.IsCollapsed = !this.IsCollapsed;\n this.CollapsedChange.emit(this.IsCollapsed);\n }\n }\n\n /**\n * Handle the accordion panel's ExpandedChange. Accordion `Expanded` is the\n * inverse of our `IsCollapsed`; preserves the CollapsedChange emit side-effect.\n */\n OnPanelExpandedChange(expanded: boolean): void {\n this.IsCollapsed = !expanded;\n this.CollapsedChange.emit(this.IsCollapsed);\n }\n\n /**\n * Handle click on an aggregate card\n */\n OnAggregateClick(agg: ViewGridAggregate): void {\n this.AggregateClick.emit(agg);\n }\n\n /**\n * Get the formatted value for an aggregate\n */\n FormatValue(agg: ViewGridAggregate): string {\n const key = agg.id || agg.expression;\n const value = this.Values.get(key);\n\n if (value == null) return '—';\n\n const format = agg.format || {};\n // Only a value that is itself a date takes the date path. `COUNT(IntakeDate)` also resolves\n // to the date column, but its value is the count — a number — and must format as one.\n const field = AggregateField(agg, this.Entity);\n if (field?.TSType === EntityFieldTSType.Date && (value instanceof Date || typeof value === 'string')) {\n return this.formatDateField(value, field.Type, format);\n }\n return this.formatByType(value, format);\n }\n\n /**\n * A date-family aggregate whose column type is known. The value may be a Date or, over\n * GraphQL, the ISO string the server serialised it to. A `date` column is a calendar day at UTC\n * midnight and is rendered in UTC so the day does not shift west of Greenwich; a timestamp is an\n * instant and is rendered in local time.\n */\n private formatDateField(value: string | Date, sqlType: string, format: AggregateValueFormat): string {\n const date = value instanceof Date ? value : new Date(value);\n if (isNaN(date.getTime())) return String(value);\n if (IsDateOnlySQLType(sqlType)) {\n return FormatDateOnly(date, this.dateOptions(format.dateFormat), 'en-US');\n }\n return this.formatDate(date, format.dateFormat);\n }\n\n /**\n * Get the CSS class for conditional styling\n */\n GetStyleClass(agg: ViewGridAggregate): string {\n const key = agg.id || agg.expression;\n const value = this.Values.get(key);\n\n if (value == null || !agg.conditionalStyles?.length) return '';\n\n for (const rule of agg.conditionalStyles) {\n if (this.evaluateCondition(value, rule)) {\n return `style-${rule.style}`;\n }\n }\n return '';\n }\n\n /**\n * Get enabled aggregates (filter out disabled ones)\n */\n get EnabledAggregates(): ViewGridAggregate[] {\n return this.Aggregates.filter(a => a.enabled !== false);\n }\n\n /**\n * Get card aggregates sorted by order\n */\n get CardAggregates(): ViewGridAggregate[] {\n return this.EnabledAggregates\n .filter(a => a.displayType === 'card')\n .sort((a, b) => (a.order || 0) - (b.order || 0));\n }\n\n /**\n * Get panel style for right position\n */\n get PanelStyle(): Record<string, string> {\n if (this.Position === 'right') {\n return {\n width: `${this.Width}px`,\n minWidth: `${this.Width}px`\n };\n }\n return {};\n }\n\n /**\n * Get grid template columns for grid layout\n */\n get GridTemplateColumns(): string {\n if (this.Layout === 'grid') {\n return `repeat(${this.GridColumns}, 1fr)`;\n }\n return '';\n }\n\n /**\n * Format value based on type configuration\n */\n private formatByType(value: AggregateValue, format: AggregateValueFormat): string {\n if (value == null) return '—';\n\n // Handle prefix/suffix\n const prefix = format.prefix || '';\n const suffix = format.suffix || '';\n\n // Handle different value types\n if (typeof value === 'number') {\n const formatted = this.formatNumber(value, format);\n return `${prefix}${formatted}${suffix}`;\n }\n\n if (value instanceof Date) {\n return this.formatDate(value, format.dateFormat);\n }\n\n if (typeof value === 'boolean') {\n return value ? 'Yes' : 'No';\n }\n\n // String or unknown type\n return `${prefix}${String(value)}${suffix}`;\n }\n\n /**\n * Format a number value\n */\n private formatNumber(value: number, format: AggregateValueFormat): string {\n const options: Intl.NumberFormatOptions = {\n minimumFractionDigits: format.decimals ?? 0,\n maximumFractionDigits: format.decimals ?? 2,\n useGrouping: format.thousandsSeparator !== false\n };\n\n // Handle currency\n if (format.currencyCode) {\n options.style = 'currency';\n options.currency = format.currencyCode;\n }\n\n try {\n return new Intl.NumberFormat('en-US', options).format(value);\n } catch {\n return String(value);\n }\n }\n\n /**\n * Format a date value\n */\n private formatDate(value: Date, dateFormat?: string): string {\n try {\n return new Intl.DateTimeFormat('en-US', this.dateOptions(dateFormat)).format(value);\n } catch {\n return String(value);\n }\n }\n\n /** The Intl options for a simple date format preset. */\n private dateOptions(dateFormat?: string): Intl.DateTimeFormatOptions {\n switch (dateFormat) {\n case 'short':\n return { dateStyle: 'short' };\n case 'long':\n return { dateStyle: 'long' };\n case 'medium':\n default:\n return { dateStyle: 'medium' };\n }\n }\n\n /**\n * Evaluate a conditional styling rule\n */\n private evaluateCondition(value: AggregateValue, rule: AggregateConditionalStyle): boolean {\n // For non-numeric values, only support equality checks\n if (typeof value !== 'number') {\n if (rule.operator === 'eq') return value === rule.value;\n if (rule.operator === 'neq') return value !== rule.value;\n return false;\n }\n\n const numValue = Number(rule.value);\n const numValue2 = rule.value2 != null ? Number(rule.value2) : numValue;\n\n switch (rule.operator) {\n case 'gt': return value > numValue;\n case 'gte': return value >= numValue;\n case 'lt': return value < numValue;\n case 'lte': return value <= numValue;\n case 'eq': return value === numValue;\n case 'neq': return value !== numValue;\n case 'between': return value >= numValue && value <= numValue2;\n default: return false;\n }\n }\n}\n","<div class=\"aggregate-panel\"\n [class.position-right]=\"Position === 'right'\"\n [class.position-bottom]=\"Position === 'bottom'\"\n [ngStyle]=\"PanelStyle\">\n\n <!-- Migrated to <mj-accordion-panel>. The bespoke .panel-header / .collapse-toggle\n and the @if(!IsCollapsed) body gate are replaced by the accordion's own header,\n chevron, and lazy/animated body. Expanded is the inverse of IsCollapsed; the\n (ExpandedChange) handler preserves the CollapsedChange emit side-effect. -->\n <mj-accordion-panel\n Size=\"sm\"\n [FlushBody]=\"true\"\n [Expanded]=\"!IsCollapsed\"\n [Disabled]=\"!Collapsible\"\n (ExpandedChange)=\"OnPanelExpandedChange($event)\">\n\n <ng-template mjAccordionTitle>\n <span class=\"panel-title\">{{ Title }}</span>\n </ng-template>\n\n <!-- Loading indicator lives in the header actions slot (sibling of the chevron). -->\n <ng-template mjAccordionActions>\n @if (Loading) {\n <span class=\"loading-indicator\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n </span>\n }\n </ng-template>\n\n <ng-template mjAccordionBody>\n <div class=\"panel-content\">\n <!-- Empty state -->\n @if (CardAggregates.length === 0) {\n <mj-empty-state\n Size=\"compact\"\n Icon=\"fa-solid fa-chart-simple\"\n Title=\"No summaries configured\" />\n }\n <!-- Cards Container -->\n <div class=\"cards-container\"\n [class.layout-horizontal]=\"Layout === 'horizontal'\"\n [class.layout-vertical]=\"Layout === 'vertical'\"\n [class.layout-grid]=\"Layout === 'grid'\"\n [ngStyle]=\"Layout === 'grid' ? {'grid-template-columns': GridTemplateColumns} : {}\">\n <!-- Individual Aggregate Cards -->\n @for (agg of CardAggregates; track agg) {\n <div class=\"aggregate-card\"\n [class]=\"GetStyleClass(agg)\"\n (click)=\"OnAggregateClick(agg)\"\n [title]=\"agg.description || agg.label\">\n <!-- Icon -->\n @if (agg.icon) {\n <div class=\"card-icon\">\n <i [class]=\"agg.icon\"></i>\n </div>\n }\n <!-- Content -->\n <div class=\"card-content\">\n <div class=\"card-value\" [class.loading]=\"Loading\">\n {{ Loading ? '...' : FormatValue(agg) }}\n </div>\n <div class=\"card-label\">{{ agg.label }}</div>\n </div>\n </div>\n }\n </div>\n </div>\n </ng-template>\n </mj-accordion-panel>\n</div>\n"]}
|
|
@@ -184,6 +184,7 @@ export declare class EntityCardsComponent extends BaseAngularComponent implement
|
|
|
184
184
|
getBooleanValue(record: Record<string, unknown>, fieldName: string): boolean;
|
|
185
185
|
getTextValue(record: Record<string, unknown>, fieldName: string, maxLength?: number): string;
|
|
186
186
|
getDateValue(record: Record<string, unknown>, fieldName: string): string;
|
|
187
|
+
private isDateOnlyField;
|
|
187
188
|
/**
|
|
188
189
|
* Get display label for a field using EntityFieldInfo's built-in DisplayNameOrName property
|
|
189
190
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-cards.component.d.ts","sourceRoot":"","sources":["../../../src/lib/entity-cards/entity-cards.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAqB,MAAM,eAAe,CAAC;AAC7K,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"entity-cards.component.d.ts","sourceRoot":"","sources":["../../../src/lib/entity-cards/entity-cards.component.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
|
|
2
2
|
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
3
|
-
import { EntityFieldValueListType, RunView, CoerceImageSrc } from '@memberjunction/core';
|
|
3
|
+
import { EntityFieldValueListType, RunView, CoerceImageSrc, IsDateOnlySQLType, FormatDateOnly } from '@memberjunction/core';
|
|
4
4
|
import { buildCompositeKey, buildPkString, computeFieldsList } from '../utils/record.util';
|
|
5
5
|
import { PillColorUtil } from '../pill/pill.component';
|
|
6
6
|
import { HighlightUtil } from '../utils/highlight.util';
|
|
@@ -600,12 +600,21 @@ export class EntityCardsComponent extends BaseAngularComponent {
|
|
|
600
600
|
const date = value instanceof Date ? value : new Date(value);
|
|
601
601
|
if (isNaN(date.getTime()))
|
|
602
602
|
return String(value);
|
|
603
|
-
|
|
603
|
+
const options = { month: 'short', day: 'numeric', year: 'numeric' };
|
|
604
|
+
// A `date` column is a calendar day that arrives as UTC midnight; a local-zone formatter
|
|
605
|
+
// would land on the previous day for every reader west of Greenwich (MJ#4210). A timestamp
|
|
606
|
+
// names an instant and stays in local time.
|
|
607
|
+
if (this.isDateOnlyField(fieldName))
|
|
608
|
+
return FormatDateOnly(date, options);
|
|
609
|
+
return date.toLocaleDateString(undefined, options);
|
|
604
610
|
}
|
|
605
611
|
catch {
|
|
606
612
|
return String(value);
|
|
607
613
|
}
|
|
608
614
|
}
|
|
615
|
+
isDateOnlyField(fieldName) {
|
|
616
|
+
return IsDateOnlySQLType(this.entity?.Fields.find(f => f.Name === fieldName)?.Type);
|
|
617
|
+
}
|
|
609
618
|
/**
|
|
610
619
|
* Get display label for a field using EntityFieldInfo's built-in DisplayNameOrName property
|
|
611
620
|
*/
|