@memberjunction/ng-dashboards 5.26.0 → 5.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts +96 -0
  2. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.d.ts.map +1 -0
  3. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js +710 -0
  4. package/dist/AI/components/analytics/agent-runs/agent-run-analysis.component.js.map +1 -0
  5. package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts +52 -0
  6. package/dist/AI/components/analytics/ai-analytics-resource.component.d.ts.map +1 -0
  7. package/dist/AI/components/analytics/ai-analytics-resource.component.js +356 -0
  8. package/dist/AI/components/analytics/ai-analytics-resource.component.js.map +1 -0
  9. package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts +52 -0
  10. package/dist/AI/components/analytics/analytics-filter-bar.component.d.ts.map +1 -0
  11. package/dist/AI/components/analytics/analytics-filter-bar.component.js +306 -0
  12. package/dist/AI/components/analytics/analytics-filter-bar.component.js.map +1 -0
  13. package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts +81 -0
  14. package/dist/AI/components/analytics/cost-budget/cost-budget.component.d.ts.map +1 -0
  15. package/dist/AI/components/analytics/cost-budget/cost-budget.component.js +744 -0
  16. package/dist/AI/components/analytics/cost-budget/cost-budget.component.js.map +1 -0
  17. package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts +61 -0
  18. package/dist/AI/components/analytics/error-analysis/error-analysis.component.d.ts.map +1 -0
  19. package/dist/AI/components/analytics/error-analysis/error-analysis.component.js +490 -0
  20. package/dist/AI/components/analytics/error-analysis/error-analysis.component.js.map +1 -0
  21. package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts +77 -0
  22. package/dist/AI/components/analytics/executive-summary/executive-summary.component.d.ts.map +1 -0
  23. package/dist/AI/components/analytics/executive-summary/executive-summary.component.js +673 -0
  24. package/dist/AI/components/analytics/executive-summary/executive-summary.component.js.map +1 -0
  25. package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts +65 -0
  26. package/dist/AI/components/analytics/model-performance/model-performance.component.d.ts.map +1 -0
  27. package/dist/AI/components/analytics/model-performance/model-performance.component.js +537 -0
  28. package/dist/AI/components/analytics/model-performance/model-performance.component.js.map +1 -0
  29. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts +131 -0
  30. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.d.ts.map +1 -0
  31. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js +1030 -0
  32. package/dist/AI/components/analytics/prompt-runs/prompt-run-analysis.component.js.map +1 -0
  33. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts +78 -0
  34. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.d.ts.map +1 -0
  35. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js +569 -0
  36. package/dist/AI/components/analytics/usage-patterns/usage-patterns.component.js.map +1 -0
  37. package/dist/AI/components/execution-monitoring.component.d.ts.map +1 -1
  38. package/dist/AI/components/execution-monitoring.component.js +4 -14
  39. package/dist/AI/components/execution-monitoring.component.js.map +1 -1
  40. package/dist/AI/components/overview/ai-overview-hub.component.d.ts +58 -0
  41. package/dist/AI/components/overview/ai-overview-hub.component.d.ts.map +1 -0
  42. package/dist/AI/components/overview/ai-overview-hub.component.js +315 -0
  43. package/dist/AI/components/overview/ai-overview-hub.component.js.map +1 -0
  44. package/dist/AI/components/prompts/prompt-management.component.js +1 -1
  45. package/dist/AI/components/prompts/prompt-management.component.js.map +1 -1
  46. package/dist/AI/index.d.ts +11 -0
  47. package/dist/AI/index.d.ts.map +1 -1
  48. package/dist/AI/index.js +13 -0
  49. package/dist/AI/index.js.map +1 -1
  50. package/dist/AI/interfaces/analytics-preferences.interface.d.ts +50 -0
  51. package/dist/AI/interfaces/analytics-preferences.interface.d.ts.map +1 -0
  52. package/dist/AI/interfaces/analytics-preferences.interface.js +9 -0
  53. package/dist/AI/interfaces/analytics-preferences.interface.js.map +1 -0
  54. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  55. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -1
  56. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  57. package/dist/Home/home-dashboard.component.js +2 -2
  58. package/dist/MCP/index.d.ts +1 -0
  59. package/dist/MCP/index.d.ts.map +1 -1
  60. package/dist/MCP/index.js +2 -0
  61. package/dist/MCP/index.js.map +1 -1
  62. package/dist/MCP/mcp-dashboard.component.d.ts +1 -0
  63. package/dist/MCP/mcp-dashboard.component.d.ts.map +1 -1
  64. package/dist/MCP/mcp-dashboard.component.js +5 -4
  65. package/dist/MCP/mcp-dashboard.component.js.map +1 -1
  66. package/dist/MCP/mcp-resource.component.d.ts +6 -5
  67. package/dist/MCP/mcp-resource.component.d.ts.map +1 -1
  68. package/dist/MCP/mcp-resource.component.js +7 -8
  69. package/dist/MCP/mcp-resource.component.js.map +1 -1
  70. package/dist/ai-dashboards.module.d.ts +27 -17
  71. package/dist/ai-dashboards.module.d.ts.map +1 -1
  72. package/dist/ai-dashboards.module.js +66 -3
  73. package/dist/ai-dashboards.module.js.map +1 -1
  74. package/dist/public-api.d.ts +1 -1
  75. package/dist/public-api.d.ts.map +1 -1
  76. package/dist/public-api.js +1 -1
  77. package/dist/public-api.js.map +1 -1
  78. package/package.json +48 -48
  79. package/dist/__tests__/analytics-resource.test.d.ts +0 -2
  80. package/dist/__tests__/analytics-resource.test.d.ts.map +0 -1
  81. package/dist/__tests__/analytics-resource.test.js +0 -181
  82. package/dist/__tests__/analytics-resource.test.js.map +0 -1
  83. package/dist/__tests__/dashboards.test.d.ts +0 -2
  84. package/dist/__tests__/dashboards.test.d.ts.map +0 -1
  85. package/dist/__tests__/dashboards.test.js +0 -40
  86. package/dist/__tests__/dashboards.test.js.map +0 -1
  87. package/dist/__tests__/integration-data-service.test.d.ts +0 -2
  88. package/dist/__tests__/integration-data-service.test.d.ts.map +0 -1
  89. package/dist/__tests__/integration-data-service.test.js +0 -132
  90. package/dist/__tests__/integration-data-service.test.js.map +0 -1
  91. package/dist/__tests__/mapping-validation.test.d.ts +0 -2
  92. package/dist/__tests__/mapping-validation.test.d.ts.map +0 -1
  93. package/dist/__tests__/mapping-validation.test.js +0 -170
  94. package/dist/__tests__/mapping-validation.test.js.map +0 -1
  95. package/dist/__tests__/scheduling.test.d.ts +0 -2
  96. package/dist/__tests__/scheduling.test.d.ts.map +0 -1
  97. package/dist/__tests__/scheduling.test.js +0 -205
  98. package/dist/__tests__/scheduling.test.js.map +0 -1
@@ -0,0 +1,569 @@
1
+ /**
2
+ * @fileoverview Usage Patterns Analytics.
3
+ *
4
+ * Displays a time-of-day heatmap (7 days × 24 hours), day-of-week distribution bars,
5
+ * peak hours summary cards, and an hourly throughput bar chart.
6
+ * Data loaded from MJ: AI Prompt Runs for the last 30 days.
7
+ */
8
+ import { Component, Input, Output, EventEmitter, ChangeDetectorRef, inject } from '@angular/core';
9
+ import { Subject } from 'rxjs';
10
+ import { RunView } from '@memberjunction/core';
11
+ import * as i0 from "@angular/core";
12
+ import * as i1 from "@memberjunction/ng-shared-generic";
13
+ import * as i2 from "../analytics-filter-bar.component";
14
+ import * as i3 from "@angular/common";
15
+ const _forTrack0 = ($index, $item) => $item.DayIndex;
16
+ const _forTrack1 = ($index, $item) => $item.Label;
17
+ const _forTrack2 = ($index, $item) => $item.Hour;
18
+ function AnalyticsUsagePatternsComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
19
+ i0.ɵɵelementStart(0, "div", 1);
20
+ i0.ɵɵelement(1, "mj-loading", 3);
21
+ i0.ɵɵelementEnd();
22
+ } }
23
+ function AnalyticsUsagePatternsComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
24
+ i0.ɵɵelementStart(0, "div", 2);
25
+ i0.ɵɵelement(1, "i", 4);
26
+ i0.ɵɵelementStart(2, "div", 5);
27
+ i0.ɵɵtext(3, "No Data Available");
28
+ i0.ɵɵelementEnd();
29
+ i0.ɵɵelementStart(4, "div", 6);
30
+ i0.ɵɵtext(5, "No prompt runs found in the last 30 days.");
31
+ i0.ɵɵelementEnd()();
32
+ } }
33
+ function AnalyticsUsagePatternsComponent_Conditional_3_For_10_Template(rf, ctx) { if (rf & 1) {
34
+ i0.ɵɵelementStart(0, "div", 14);
35
+ i0.ɵɵtext(1);
36
+ i0.ɵɵelementEnd();
37
+ } if (rf & 2) {
38
+ const h_r1 = ctx.$implicit;
39
+ i0.ɵɵadvance();
40
+ i0.ɵɵtextInterpolate(h_r1);
41
+ } }
42
+ function AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Conditional_1_Template(rf, ctx) { if (rf & 1) {
43
+ i0.ɵɵelementStart(0, "span", 26);
44
+ i0.ɵɵtext(1);
45
+ i0.ɵɵelementEnd();
46
+ } if (rf & 2) {
47
+ const h_r2 = i0.ɵɵnextContext().$implicit;
48
+ const ɵ$index_40_r3 = i0.ɵɵnextContext().$index;
49
+ const ctx_r3 = i0.ɵɵnextContext(2);
50
+ i0.ɵɵadvance();
51
+ i0.ɵɵtextInterpolate(ctx_r3.getCellCount(ɵ$index_40_r3, h_r2));
52
+ } }
53
+ function AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Template(rf, ctx) { if (rf & 1) {
54
+ i0.ɵɵelementStart(0, "div", 25);
55
+ i0.ɵɵconditionalCreate(1, AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Conditional_1_Template, 2, 1, "span", 26);
56
+ i0.ɵɵelementEnd();
57
+ } if (rf & 2) {
58
+ const h_r2 = ctx.$implicit;
59
+ const ɵ$index_40_r3 = i0.ɵɵnextContext().$index;
60
+ const ctx_r3 = i0.ɵɵnextContext(2);
61
+ i0.ɵɵstyleProp("background", ctx_r3.getCellBackground(ɵ$index_40_r3, h_r2));
62
+ i0.ɵɵproperty("title", ctx_r3.getCellTooltip(ɵ$index_40_r3, h_r2));
63
+ i0.ɵɵadvance();
64
+ i0.ɵɵconditional(ctx_r3.getCellCount(ɵ$index_40_r3, h_r2) > 0 ? 1 : -1);
65
+ } }
66
+ function AnalyticsUsagePatternsComponent_Conditional_3_For_12_Template(rf, ctx) { if (rf & 1) {
67
+ i0.ɵɵelementStart(0, "div", 23);
68
+ i0.ɵɵtext(1);
69
+ i0.ɵɵelementEnd();
70
+ i0.ɵɵrepeaterCreate(2, AnalyticsUsagePatternsComponent_Conditional_3_For_12_For_3_Template, 2, 4, "div", 24, i0.ɵɵrepeaterTrackByIdentity);
71
+ } if (rf & 2) {
72
+ const day_r5 = ctx.$implicit;
73
+ const ctx_r3 = i0.ɵɵnextContext(2);
74
+ i0.ɵɵadvance();
75
+ i0.ɵɵtextInterpolate(day_r5);
76
+ i0.ɵɵadvance();
77
+ i0.ɵɵrepeater(ctx_r3.Hours);
78
+ } }
79
+ function AnalyticsUsagePatternsComponent_Conditional_3_For_20_Template(rf, ctx) { if (rf & 1) {
80
+ i0.ɵɵelementStart(0, "div", 17)(1, "span", 27);
81
+ i0.ɵɵtext(2);
82
+ i0.ɵɵelementEnd();
83
+ i0.ɵɵelementStart(3, "div", 28);
84
+ i0.ɵɵelement(4, "div", 29);
85
+ i0.ɵɵelementEnd();
86
+ i0.ɵɵelementStart(5, "span", 30);
87
+ i0.ɵɵtext(6);
88
+ i0.ɵɵpipe(7, "number");
89
+ i0.ɵɵelementEnd()();
90
+ } if (rf & 2) {
91
+ const d_r6 = ctx.$implicit;
92
+ i0.ɵɵadvance(2);
93
+ i0.ɵɵtextInterpolate(d_r6.DayName);
94
+ i0.ɵɵadvance(2);
95
+ i0.ɵɵstyleProp("width", d_r6.WidthPercent, "%");
96
+ i0.ɵɵadvance(2);
97
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind1(7, 4, d_r6.Count));
98
+ } }
99
+ function AnalyticsUsagePatternsComponent_Conditional_3_For_27_Template(rf, ctx) { if (rf & 1) {
100
+ i0.ɵɵelementStart(0, "div", 19)(1, "div", 31);
101
+ i0.ɵɵelement(2, "i");
102
+ i0.ɵɵelementEnd();
103
+ i0.ɵɵelementStart(3, "div", 32)(4, "div", 33);
104
+ i0.ɵɵtext(5);
105
+ i0.ɵɵelementEnd();
106
+ i0.ɵɵelementStart(6, "div", 34);
107
+ i0.ɵɵtext(7);
108
+ i0.ɵɵelementEnd();
109
+ i0.ɵɵelementStart(8, "div", 35);
110
+ i0.ɵɵtext(9);
111
+ i0.ɵɵpipe(10, "number");
112
+ i0.ɵɵelementEnd()()();
113
+ } if (rf & 2) {
114
+ const peak_r7 = ctx.$implicit;
115
+ i0.ɵɵadvance(2);
116
+ i0.ɵɵclassMap(peak_r7.Icon);
117
+ i0.ɵɵadvance(3);
118
+ i0.ɵɵtextInterpolate(peak_r7.Label);
119
+ i0.ɵɵadvance(2);
120
+ i0.ɵɵtextInterpolate(peak_r7.Value);
121
+ i0.ɵɵadvance(2);
122
+ i0.ɵɵtextInterpolate1("", i0.ɵɵpipeBind1(10, 5, peak_r7.Count), " runs");
123
+ } }
124
+ function AnalyticsUsagePatternsComponent_Conditional_3_For_37_Template(rf, ctx) { if (rf & 1) {
125
+ i0.ɵɵelementStart(0, "div", 22);
126
+ i0.ɵɵelement(1, "div", 36);
127
+ i0.ɵɵelementStart(2, "span", 37);
128
+ i0.ɵɵtext(3);
129
+ i0.ɵɵelementEnd()();
130
+ } if (rf & 2) {
131
+ const bar_r8 = ctx.$implicit;
132
+ i0.ɵɵproperty("title", bar_r8.Label + ": " + bar_r8.Count + " runs");
133
+ i0.ɵɵadvance();
134
+ i0.ɵɵstyleProp("height", bar_r8.HeightPercent, "%");
135
+ i0.ɵɵadvance(2);
136
+ i0.ɵɵtextInterpolate(bar_r8.Label);
137
+ } }
138
+ function AnalyticsUsagePatternsComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
139
+ i0.ɵɵelementStart(0, "div", 7)(1, "div", 8)(2, "h4", 9);
140
+ i0.ɵɵtext(3, "Time-of-Day Heatmap");
141
+ i0.ɵɵelementEnd();
142
+ i0.ɵɵelementStart(4, "span", 10);
143
+ i0.ɵɵtext(5, "Execution count by day and hour");
144
+ i0.ɵɵelementEnd()();
145
+ i0.ɵɵelementStart(6, "div", 11)(7, "div", 12);
146
+ i0.ɵɵelement(8, "div", 13);
147
+ i0.ɵɵrepeaterCreate(9, AnalyticsUsagePatternsComponent_Conditional_3_For_10_Template, 2, 1, "div", 14, i0.ɵɵrepeaterTrackByIdentity);
148
+ i0.ɵɵrepeaterCreate(11, AnalyticsUsagePatternsComponent_Conditional_3_For_12_Template, 4, 1, null, null, i0.ɵɵrepeaterTrackByIdentity);
149
+ i0.ɵɵelementEnd()()();
150
+ i0.ɵɵelementStart(13, "div", 15)(14, "div", 7)(15, "div", 8)(16, "h4", 9);
151
+ i0.ɵɵtext(17, "Day-of-Week Distribution");
152
+ i0.ɵɵelementEnd()();
153
+ i0.ɵɵelementStart(18, "div", 16);
154
+ i0.ɵɵrepeaterCreate(19, AnalyticsUsagePatternsComponent_Conditional_3_For_20_Template, 8, 6, "div", 17, _forTrack0);
155
+ i0.ɵɵelementEnd()();
156
+ i0.ɵɵelementStart(21, "div", 7)(22, "div", 8)(23, "h4", 9);
157
+ i0.ɵɵtext(24, "Peak Hours Summary");
158
+ i0.ɵɵelementEnd()();
159
+ i0.ɵɵelementStart(25, "div", 18);
160
+ i0.ɵɵrepeaterCreate(26, AnalyticsUsagePatternsComponent_Conditional_3_For_27_Template, 11, 7, "div", 19, _forTrack1);
161
+ i0.ɵɵelementEnd()()();
162
+ i0.ɵɵelementStart(28, "div", 7)(29, "div", 8)(30, "h4", 9);
163
+ i0.ɵɵtext(31, "Hourly Throughput");
164
+ i0.ɵɵelementEnd();
165
+ i0.ɵɵelementStart(32, "span", 10);
166
+ i0.ɵɵtext(33, "Total runs per hour (all days combined)");
167
+ i0.ɵɵelementEnd()();
168
+ i0.ɵɵelementStart(34, "div", 20)(35, "div", 21);
169
+ i0.ɵɵrepeaterCreate(36, AnalyticsUsagePatternsComponent_Conditional_3_For_37_Template, 4, 4, "div", 22, _forTrack2);
170
+ i0.ɵɵelementEnd()()();
171
+ } if (rf & 2) {
172
+ const ctx_r3 = i0.ɵɵnextContext();
173
+ i0.ɵɵadvance(9);
174
+ i0.ɵɵrepeater(ctx_r3.Hours);
175
+ i0.ɵɵadvance(2);
176
+ i0.ɵɵrepeater(ctx_r3.DayNames);
177
+ i0.ɵɵadvance(8);
178
+ i0.ɵɵrepeater(ctx_r3.DayDistributions);
179
+ i0.ɵɵadvance(7);
180
+ i0.ɵɵrepeater(ctx_r3.PeakSummaries);
181
+ i0.ɵɵadvance(10);
182
+ i0.ɵɵrepeater(ctx_r3.HourlyBars);
183
+ } }
184
+ const DAY_NAMES = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
185
+ const HOURS = Array.from({ length: 24 }, (_, i) => i);
186
+ const FIELDS = [
187
+ 'ID', 'RunAt', 'Success', 'Cost', 'TokensUsed', 'ExecutionTimeMS'
188
+ ];
189
+ export class AnalyticsUsagePatternsComponent {
190
+ _timeRange = '30d';
191
+ isInitialized = false;
192
+ set TimeRange(value) {
193
+ if (value !== this._timeRange) {
194
+ this._timeRange = value;
195
+ if (this.isInitialized) {
196
+ this.loadData();
197
+ }
198
+ }
199
+ }
200
+ get TimeRange() { return this._timeRange; }
201
+ TimeRangeChange = new EventEmitter();
202
+ // ── Public state ──
203
+ IsLoading = true;
204
+ TotalRuns = 0;
205
+ HeatmapCells = []; // [day][hour]
206
+ DayDistributions = [];
207
+ PeakSummaries = [];
208
+ HourlyBars = [];
209
+ DayNames = DAY_NAMES;
210
+ Hours = HOURS;
211
+ // ── Private ──
212
+ destroy$ = new Subject();
213
+ cdr = inject(ChangeDetectorRef);
214
+ async ngOnInit() {
215
+ await this.loadData();
216
+ this.isInitialized = true;
217
+ }
218
+ OnTimeRangeChange(range) {
219
+ this.TimeRange = range;
220
+ this.TimeRangeChange.emit(range);
221
+ }
222
+ ngOnDestroy() {
223
+ this.destroy$.next();
224
+ this.destroy$.complete();
225
+ }
226
+ // ── Heatmap helpers (called from template) ──
227
+ getCellBackground(day, hour) {
228
+ const intensity = this.HeatmapCells[day]?.[hour]?.Intensity ?? 0;
229
+ return `color-mix(in srgb, var(--mj-brand-primary) ${intensity}%, var(--mj-bg-surface))`;
230
+ }
231
+ getCellTooltip(day, hour) {
232
+ const count = this.HeatmapCells[day]?.[hour]?.Count ?? 0;
233
+ return `${DAY_NAMES[day]} ${this.formatHourLabel(hour)}: ${count} runs`;
234
+ }
235
+ getCellCount(day, hour) {
236
+ return this.HeatmapCells[day]?.[hour]?.Count ?? 0;
237
+ }
238
+ // ── Data Loading ──
239
+ async loadData() {
240
+ this.IsLoading = true;
241
+ this.cdr.detectChanges();
242
+ try {
243
+ const runs = await this.fetchPromptRuns();
244
+ this.TotalRuns = runs.length;
245
+ if (runs.length > 0) {
246
+ const dayCounts = this.buildDayHourCounts(runs);
247
+ this.HeatmapCells = this.buildHeatmap(dayCounts);
248
+ this.DayDistributions = this.buildDayDistributions(dayCounts);
249
+ this.HourlyBars = this.buildHourlyBars(dayCounts);
250
+ this.PeakSummaries = this.buildPeakSummaries(dayCounts);
251
+ }
252
+ }
253
+ catch (err) {
254
+ console.error('Usage patterns data load failed:', err);
255
+ }
256
+ finally {
257
+ this.IsLoading = false;
258
+ this.cdr.detectChanges();
259
+ }
260
+ }
261
+ async fetchPromptRuns() {
262
+ const rv = new RunView();
263
+ const since = new Date();
264
+ switch (this.TimeRange) {
265
+ case '1h':
266
+ since.setHours(since.getHours() - 1);
267
+ break;
268
+ case '6h':
269
+ since.setHours(since.getHours() - 6);
270
+ break;
271
+ case '24h':
272
+ since.setDate(since.getDate() - 1);
273
+ break;
274
+ case '7d':
275
+ since.setDate(since.getDate() - 7);
276
+ break;
277
+ case '30d':
278
+ default:
279
+ since.setDate(since.getDate() - 30);
280
+ break;
281
+ }
282
+ const sinceStr = since.toISOString();
283
+ const result = await rv.RunView({
284
+ EntityName: 'MJ: AI Prompt Runs',
285
+ ExtraFilter: `RunAt >= '${sinceStr}'`,
286
+ Fields: FIELDS,
287
+ ResultType: 'simple'
288
+ });
289
+ if (!result.Success) {
290
+ console.error('Failed to load prompt runs:', result.ErrorMessage);
291
+ return [];
292
+ }
293
+ return result.Results ?? [];
294
+ }
295
+ /**
296
+ * Build a 7×24 count matrix: dayCounts[dayIndex][hour] = count.
297
+ * dayIndex 0 = Monday, 6 = Sunday. Uses the JS Date getDay() offset.
298
+ */
299
+ buildDayHourCounts(runs) {
300
+ const counts = Array.from({ length: 7 }, () => new Array(24).fill(0));
301
+ for (const run of runs) {
302
+ const dt = new Date(run.RunAt);
303
+ const jsDay = dt.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
304
+ const dayIndex = jsDay === 0 ? 6 : jsDay - 1; // Convert to 0=Mon, 6=Sun
305
+ const hour = dt.getHours();
306
+ counts[dayIndex][hour]++;
307
+ }
308
+ return counts;
309
+ }
310
+ buildHeatmap(dayCounts) {
311
+ const maxCount = this.findMaxCount(dayCounts);
312
+ return dayCounts.map((hours, day) => hours.map((count, hour) => ({
313
+ Day: day,
314
+ Hour: hour,
315
+ Count: count,
316
+ Intensity: maxCount > 0 ? Math.round((count / maxCount) * 100) : 0
317
+ })));
318
+ }
319
+ buildDayDistributions(dayCounts) {
320
+ const daySums = dayCounts.map(hours => hours.reduce((sum, c) => sum + c, 0));
321
+ const maxDay = Math.max(...daySums, 1);
322
+ return daySums.map((count, i) => ({
323
+ DayIndex: i,
324
+ DayName: DAY_NAMES[i],
325
+ Count: count,
326
+ WidthPercent: Math.round((count / maxDay) * 100)
327
+ }));
328
+ }
329
+ buildHourlyBars(dayCounts) {
330
+ const hourSums = HOURS.map(h => dayCounts.reduce((sum, dayHours) => sum + dayHours[h], 0));
331
+ const maxHour = Math.max(...hourSums, 1);
332
+ return hourSums.map((count, h) => ({
333
+ Hour: h,
334
+ Label: this.formatHourLabel(h),
335
+ Count: count,
336
+ HeightPercent: Math.round((count / maxHour) * 100)
337
+ }));
338
+ }
339
+ buildPeakSummaries(dayCounts) {
340
+ const hourSums = HOURS.map(h => dayCounts.reduce((sum, dayHours) => sum + dayHours[h], 0));
341
+ const daySums = dayCounts.map(hours => hours.reduce((sum, c) => sum + c, 0));
342
+ // Busiest hour
343
+ const busiestHourIdx = this.indexOfMax(hourSums);
344
+ const busiestHour = {
345
+ Label: 'Busiest Hour',
346
+ Value: `${this.formatHourRange(busiestHourIdx)}`,
347
+ Count: hourSums[busiestHourIdx],
348
+ Icon: 'fa-solid fa-fire'
349
+ };
350
+ // Busiest day
351
+ const busiestDayIdx = this.indexOfMax(daySums);
352
+ const busiestDay = {
353
+ Label: 'Busiest Day',
354
+ Value: this.fullDayName(busiestDayIdx),
355
+ Count: daySums[busiestDayIdx],
356
+ Icon: 'fa-solid fa-calendar-day'
357
+ };
358
+ // Quietest cell (day+hour combo)
359
+ let minCount = Infinity;
360
+ let quietDay = 0;
361
+ let quietHour = 0;
362
+ for (let d = 0; d < 7; d++) {
363
+ for (let h = 0; h < 24; h++) {
364
+ if (dayCounts[d][h] < minCount) {
365
+ minCount = dayCounts[d][h];
366
+ quietDay = d;
367
+ quietHour = h;
368
+ }
369
+ }
370
+ }
371
+ const quietest = {
372
+ Label: 'Quietest Period',
373
+ Value: `${DAY_NAMES[quietDay]} ${this.formatHourLabel(quietHour)}`,
374
+ Count: minCount,
375
+ Icon: 'fa-solid fa-moon'
376
+ };
377
+ return [busiestHour, busiestDay, quietest];
378
+ }
379
+ // ── Utility helpers ──
380
+ findMaxCount(dayCounts) {
381
+ let max = 0;
382
+ for (const hours of dayCounts) {
383
+ for (const c of hours) {
384
+ if (c > max)
385
+ max = c;
386
+ }
387
+ }
388
+ return max;
389
+ }
390
+ indexOfMax(arr) {
391
+ let maxIdx = 0;
392
+ for (let i = 1; i < arr.length; i++) {
393
+ if (arr[i] > arr[maxIdx])
394
+ maxIdx = i;
395
+ }
396
+ return maxIdx;
397
+ }
398
+ formatHourLabel(h) {
399
+ if (h === 0)
400
+ return '12a';
401
+ if (h < 12)
402
+ return `${h}a`;
403
+ if (h === 12)
404
+ return '12p';
405
+ return `${h - 12}p`;
406
+ }
407
+ formatHourRange(h) {
408
+ const start = this.formatHour12(h);
409
+ const end = this.formatHour12((h + 1) % 24);
410
+ return `${start} - ${end}`;
411
+ }
412
+ formatHour12(h) {
413
+ if (h === 0)
414
+ return '12:00 AM';
415
+ if (h < 12)
416
+ return `${h}:00 AM`;
417
+ if (h === 12)
418
+ return '12:00 PM';
419
+ return `${h - 12}:00 PM`;
420
+ }
421
+ fullDayName(idx) {
422
+ const names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
423
+ return names[idx] ?? 'Unknown';
424
+ }
425
+ static ɵfac = function AnalyticsUsagePatternsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AnalyticsUsagePatternsComponent)(); };
426
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AnalyticsUsagePatternsComponent, selectors: [["app-analytics-usage-patterns"]], inputs: { TimeRange: "TimeRange" }, outputs: { TimeRangeChange: "TimeRangeChange" }, standalone: false, decls: 4, vars: 7, consts: [[3, "TimeRangeChange", "TimeRange", "ShowModelFilter", "ShowAgentFilter", "ShowPromptFilter", "ShowStatusFilter", "ShowCompareToggle"], [1, "loading-container"], [1, "empty-state"], ["text", "Analyzing usage patterns..."], [1, "fa-solid", "fa-chart-line", "empty-state__icon"], [1, "empty-state__title"], [1, "empty-state__subtitle"], [1, "panel"], [1, "panel__header"], [1, "panel__title"], [1, "panel__subtitle"], [1, "heatmap-wrapper"], [1, "heatmap-grid"], [1, "heatmap-corner"], [1, "heatmap-hour-label"], [1, "two-col-row"], [1, "day-distribution"], [1, "day-bar-row"], [1, "peak-cards"], [1, "peak-card"], [1, "hourly-chart"], [1, "hourly-bars"], [1, "hourly-bar-col", 3, "title"], [1, "heatmap-day-label"], [1, "heatmap-cell", 3, "background", "title"], [1, "heatmap-cell", 3, "title"], [1, "heatmap-cell__count"], [1, "day-bar-label"], [1, "day-bar-track"], [1, "day-bar-fill"], [1, "day-bar-count"], [1, "peak-card__icon"], [1, "peak-card__content"], [1, "peak-card__label"], [1, "peak-card__value"], [1, "peak-card__count"], [1, "hourly-bar"], [1, "hourly-bar-label"]], template: function AnalyticsUsagePatternsComponent_Template(rf, ctx) { if (rf & 1) {
427
+ i0.ɵɵelementStart(0, "app-analytics-filter-bar", 0);
428
+ i0.ɵɵlistener("TimeRangeChange", function AnalyticsUsagePatternsComponent_Template_app_analytics_filter_bar_TimeRangeChange_0_listener($event) { return ctx.OnTimeRangeChange($event); });
429
+ i0.ɵɵelementEnd();
430
+ i0.ɵɵconditionalCreate(1, AnalyticsUsagePatternsComponent_Conditional_1_Template, 2, 0, "div", 1)(2, AnalyticsUsagePatternsComponent_Conditional_2_Template, 6, 0, "div", 2)(3, AnalyticsUsagePatternsComponent_Conditional_3_Template, 38, 0);
431
+ } if (rf & 2) {
432
+ i0.ɵɵproperty("TimeRange", ctx.TimeRange)("ShowModelFilter", false)("ShowAgentFilter", false)("ShowPromptFilter", false)("ShowStatusFilter", false)("ShowCompareToggle", false);
433
+ i0.ɵɵadvance();
434
+ i0.ɵɵconditional(ctx.IsLoading ? 1 : ctx.TotalRuns === 0 ? 2 : 3);
435
+ } }, dependencies: [i1.LoadingComponent, i2.AnalyticsFilterBarComponent, i3.DecimalPipe], styles: ["[_nghost-%COMP%] { display: block; }\n\n \n\n .section-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .section-header__left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .section-header__icon[_ngcontent-%COMP%] {\n font-size: 18px;\n color: var(--mj-brand-primary);\n }\n\n .section-header__title[_ngcontent-%COMP%] {\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .time-range-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border-radius: 20px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n }\n\n \n\n .loading-container[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n }\n\n .empty-state__icon[_ngcontent-%COMP%] {\n font-size: 36px;\n color: var(--mj-text-muted);\n margin-bottom: 12px;\n }\n\n .empty-state__title[_ngcontent-%COMP%] {\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n }\n\n .empty-state__subtitle[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-muted);\n }\n\n \n\n .panel[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n margin-bottom: 16px;\n }\n\n .panel__header[_ngcontent-%COMP%] {\n display: flex;\n align-items: baseline;\n gap: 10px;\n margin-bottom: 16px;\n }\n\n .panel__title[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .panel__subtitle[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n\n \n\n .heatmap-wrapper[_ngcontent-%COMP%] {\n overflow-x: auto;\n }\n\n .heatmap-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 44px repeat(24, 1fr);\n gap: 2px;\n min-width: 600px;\n }\n\n .heatmap-corner[_ngcontent-%COMP%] {\n \n\n }\n\n .heatmap-hour-label[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-align: center;\n padding-bottom: 4px;\n font-weight: 500;\n }\n\n .heatmap-day-label[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-secondary);\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-right: 6px;\n }\n\n .heatmap-cell[_ngcontent-%COMP%] {\n min-height: 28px;\n border-radius: 3px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n transition: opacity 0.15s ease;\n }\n\n .heatmap-cell[_ngcontent-%COMP%]:hover {\n opacity: 0.8;\n outline: 1px solid var(--mj-border-strong);\n }\n\n .heatmap-cell__count[_ngcontent-%COMP%] {\n font-size: 9px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n \n\n .two-col-row[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n }\n\n @media (max-width: 1024px) {\n .two-col-row[_ngcontent-%COMP%] {\n grid-template-columns: 1fr;\n }\n }\n\n \n\n .day-distribution[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .day-bar-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .day-bar-label[_ngcontent-%COMP%] {\n width: 36px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: right;\n flex-shrink: 0;\n }\n\n .day-bar-track[_ngcontent-%COMP%] {\n flex: 1;\n height: 22px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n overflow: hidden;\n }\n\n .day-bar-fill[_ngcontent-%COMP%] {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 4px;\n transition: width 0.3s ease;\n min-width: 2px;\n }\n\n .day-bar-count[_ngcontent-%COMP%] {\n width: 48px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-align: right;\n flex-shrink: 0;\n }\n\n \n\n .peak-cards[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .peak-card[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n }\n\n .peak-card__icon[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n\n .peak-card[_ngcontent-%COMP%]:nth-child(2) .peak-card__icon[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n\n .peak-card[_ngcontent-%COMP%]:nth-child(3) .peak-card__icon[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n }\n\n .peak-card__content[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .peak-card__label[_ngcontent-%COMP%] {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n }\n\n .peak-card__value[_ngcontent-%COMP%] {\n font-size: 14px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n\n .peak-card__count[_ngcontent-%COMP%] {\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n \n\n .hourly-chart[_ngcontent-%COMP%] {\n padding-top: 8px;\n }\n\n .hourly-bars[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 3px;\n height: 160px;\n }\n\n .hourly-bar-col[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n cursor: default;\n }\n\n .hourly-bar[_ngcontent-%COMP%] {\n width: 100%;\n min-height: 2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 35%, var(--mj-bg-surface));\n border-radius: 3px 3px 0 0;\n transition: background 0.15s ease, height 0.3s ease;\n }\n\n .hourly-bar-col[_ngcontent-%COMP%]:hover .hourly-bar[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 60%, var(--mj-bg-surface));\n }\n\n .hourly-bar-label[_ngcontent-%COMP%] {\n font-size: 9px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n \n\n @media (max-width: 1024px) {\n .heatmap-grid[_ngcontent-%COMP%] {\n grid-template-columns: 36px repeat(24, 1fr);\n }\n\n .heatmap-cell[_ngcontent-%COMP%] {\n min-height: 22px;\n }\n\n .heatmap-cell__count[_ngcontent-%COMP%] {\n font-size: 8px;\n }\n\n .hourly-bars[_ngcontent-%COMP%] {\n height: 120px;\n }\n }"] });
436
+ }
437
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AnalyticsUsagePatternsComponent, [{
438
+ type: Component,
439
+ args: [{ standalone: false, selector: 'app-analytics-usage-patterns', template: `
440
+ <!-- Filter Bar -->
441
+ <app-analytics-filter-bar
442
+ [TimeRange]="TimeRange"
443
+ [ShowModelFilter]="false"
444
+ [ShowAgentFilter]="false"
445
+ [ShowPromptFilter]="false"
446
+ [ShowStatusFilter]="false"
447
+ [ShowCompareToggle]="false"
448
+ (TimeRangeChange)="OnTimeRangeChange($event)"
449
+ ></app-analytics-filter-bar>
450
+
451
+ @if (IsLoading) {
452
+ <div class="loading-container">
453
+ <mj-loading text="Analyzing usage patterns..."></mj-loading>
454
+ </div>
455
+ } @else if (TotalRuns === 0) {
456
+ <div class="empty-state">
457
+ <i class="fa-solid fa-chart-line empty-state__icon"></i>
458
+ <div class="empty-state__title">No Data Available</div>
459
+ <div class="empty-state__subtitle">No prompt runs found in the last 30 days.</div>
460
+ </div>
461
+ } @else {
462
+ <!-- Heatmap -->
463
+ <div class="panel">
464
+ <div class="panel__header">
465
+ <h4 class="panel__title">Time-of-Day Heatmap</h4>
466
+ <span class="panel__subtitle">Execution count by day and hour</span>
467
+ </div>
468
+ <div class="heatmap-wrapper">
469
+ <div class="heatmap-grid">
470
+ <!-- Corner spacer -->
471
+ <div class="heatmap-corner"></div>
472
+ <!-- Hour labels -->
473
+ @for (h of Hours; track h) {
474
+ <div class="heatmap-hour-label">{{ h }}</div>
475
+ }
476
+
477
+ <!-- Rows: one per day -->
478
+ @for (day of DayNames; track day; let d = $index) {
479
+ <div class="heatmap-day-label">{{ day }}</div>
480
+ @for (h of Hours; track h) {
481
+ <div
482
+ class="heatmap-cell"
483
+ [style.background]="getCellBackground(d, h)"
484
+ [title]="getCellTooltip(d, h)">
485
+ @if (getCellCount(d, h) > 0) {
486
+ <span class="heatmap-cell__count">{{ getCellCount(d, h) }}</span>
487
+ }
488
+ </div>
489
+ }
490
+ }
491
+ </div>
492
+ </div>
493
+ </div>
494
+
495
+ <!-- Day Distribution + Peak Summary -->
496
+ <div class="two-col-row">
497
+ <!-- Day-of-Week Distribution -->
498
+ <div class="panel">
499
+ <div class="panel__header">
500
+ <h4 class="panel__title">Day-of-Week Distribution</h4>
501
+ </div>
502
+ <div class="day-distribution">
503
+ @for (d of DayDistributions; track d.DayIndex) {
504
+ <div class="day-bar-row">
505
+ <span class="day-bar-label">{{ d.DayName }}</span>
506
+ <div class="day-bar-track">
507
+ <div
508
+ class="day-bar-fill"
509
+ [style.width.%]="d.WidthPercent">
510
+ </div>
511
+ </div>
512
+ <span class="day-bar-count">{{ d.Count | number }}</span>
513
+ </div>
514
+ }
515
+ </div>
516
+ </div>
517
+
518
+ <!-- Peak Hours Summary -->
519
+ <div class="panel">
520
+ <div class="panel__header">
521
+ <h4 class="panel__title">Peak Hours Summary</h4>
522
+ </div>
523
+ <div class="peak-cards">
524
+ @for (peak of PeakSummaries; track peak.Label) {
525
+ <div class="peak-card">
526
+ <div class="peak-card__icon">
527
+ <i [class]="peak.Icon"></i>
528
+ </div>
529
+ <div class="peak-card__content">
530
+ <div class="peak-card__label">{{ peak.Label }}</div>
531
+ <div class="peak-card__value">{{ peak.Value }}</div>
532
+ <div class="peak-card__count">{{ peak.Count | number }} runs</div>
533
+ </div>
534
+ </div>
535
+ }
536
+ </div>
537
+ </div>
538
+ </div>
539
+
540
+ <!-- Hourly Throughput -->
541
+ <div class="panel">
542
+ <div class="panel__header">
543
+ <h4 class="panel__title">Hourly Throughput</h4>
544
+ <span class="panel__subtitle">Total runs per hour (all days combined)</span>
545
+ </div>
546
+ <div class="hourly-chart">
547
+ <div class="hourly-bars">
548
+ @for (bar of HourlyBars; track bar.Hour) {
549
+ <div class="hourly-bar-col" [title]="bar.Label + ': ' + bar.Count + ' runs'">
550
+ <div
551
+ class="hourly-bar"
552
+ [style.height.%]="bar.HeightPercent">
553
+ </div>
554
+ <span class="hourly-bar-label">{{ bar.Label }}</span>
555
+ </div>
556
+ }
557
+ </div>
558
+ </div>
559
+ </div>
560
+ }
561
+ `, styles: ["\n :host { display: block; }\n\n /* \u2500\u2500 Header \u2500\u2500 */\n .section-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .section-header__left {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .section-header__icon {\n font-size: 18px;\n color: var(--mj-brand-primary);\n }\n\n .section-header__title {\n font-size: 18px;\n font-weight: 700;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .time-range-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 5px 12px;\n border-radius: 20px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n }\n\n /* \u2500\u2500 Loading / Empty \u2500\u2500 */\n .loading-container {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 300px;\n }\n\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 24px;\n text-align: center;\n }\n\n .empty-state__icon {\n font-size: 36px;\n color: var(--mj-text-muted);\n margin-bottom: 12px;\n }\n\n .empty-state__title {\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 4px;\n }\n\n .empty-state__subtitle {\n font-size: 13px;\n color: var(--mj-text-muted);\n }\n\n /* \u2500\u2500 Panels \u2500\u2500 */\n .panel {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n padding: 20px;\n margin-bottom: 16px;\n }\n\n .panel__header {\n display: flex;\n align-items: baseline;\n gap: 10px;\n margin-bottom: 16px;\n }\n\n .panel__title {\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0;\n }\n\n .panel__subtitle {\n font-size: 12px;\n color: var(--mj-text-muted);\n }\n\n /* \u2500\u2500 Heatmap \u2500\u2500 */\n .heatmap-wrapper {\n overflow-x: auto;\n }\n\n .heatmap-grid {\n display: grid;\n grid-template-columns: 44px repeat(24, 1fr);\n gap: 2px;\n min-width: 600px;\n }\n\n .heatmap-corner {\n /* empty top-left cell */\n }\n\n .heatmap-hour-label {\n font-size: 10px;\n color: var(--mj-text-muted);\n text-align: center;\n padding-bottom: 4px;\n font-weight: 500;\n }\n\n .heatmap-day-label {\n font-size: 11px;\n color: var(--mj-text-secondary);\n font-weight: 600;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding-right: 6px;\n }\n\n .heatmap-cell {\n min-height: 28px;\n border-radius: 3px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: default;\n transition: opacity 0.15s ease;\n }\n\n .heatmap-cell:hover {\n opacity: 0.8;\n outline: 1px solid var(--mj-border-strong);\n }\n\n .heatmap-cell__count {\n font-size: 9px;\n font-weight: 600;\n color: var(--mj-text-primary);\n }\n\n /* \u2500\u2500 Two Column Row \u2500\u2500 */\n .two-col-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 16px;\n }\n\n @media (max-width: 1024px) {\n .two-col-row {\n grid-template-columns: 1fr;\n }\n }\n\n /* \u2500\u2500 Day Distribution \u2500\u2500 */\n .day-distribution {\n display: flex;\n flex-direction: column;\n gap: 10px;\n }\n\n .day-bar-row {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .day-bar-label {\n width: 36px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-align: right;\n flex-shrink: 0;\n }\n\n .day-bar-track {\n flex: 1;\n height: 22px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n overflow: hidden;\n }\n\n .day-bar-fill {\n height: 100%;\n background: var(--mj-brand-primary);\n border-radius: 4px;\n transition: width 0.3s ease;\n min-width: 2px;\n }\n\n .day-bar-count {\n width: 48px;\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-text-primary);\n text-align: right;\n flex-shrink: 0;\n }\n\n /* \u2500\u2500 Peak Cards \u2500\u2500 */\n .peak-cards {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .peak-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 14px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-subtle);\n border-radius: 10px;\n }\n\n .peak-card__icon {\n width: 40px;\n height: 40px;\n border-radius: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n }\n\n .peak-card:nth-child(2) .peak-card__icon {\n background: color-mix(in srgb, var(--mj-status-success) 10%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n }\n\n .peak-card:nth-child(3) .peak-card__icon {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n }\n\n .peak-card__content {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .peak-card__label {\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n }\n\n .peak-card__value {\n font-size: 14px;\n font-weight: 700;\n color: var(--mj-text-primary);\n }\n\n .peak-card__count {\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n /* \u2500\u2500 Hourly Throughput Chart \u2500\u2500 */\n .hourly-chart {\n padding-top: 8px;\n }\n\n .hourly-bars {\n display: flex;\n align-items: flex-end;\n gap: 3px;\n height: 160px;\n }\n\n .hourly-bar-col {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: flex-end;\n height: 100%;\n cursor: default;\n }\n\n .hourly-bar {\n width: 100%;\n min-height: 2px;\n background: color-mix(in srgb, var(--mj-brand-primary) 35%, var(--mj-bg-surface));\n border-radius: 3px 3px 0 0;\n transition: background 0.15s ease, height 0.3s ease;\n }\n\n .hourly-bar-col:hover .hourly-bar {\n background: color-mix(in srgb, var(--mj-brand-primary) 60%, var(--mj-bg-surface));\n }\n\n .hourly-bar-label {\n font-size: 9px;\n color: var(--mj-text-muted);\n margin-top: 4px;\n white-space: nowrap;\n }\n\n /* \u2500\u2500 Responsive \u2500\u2500 */\n @media (max-width: 1024px) {\n .heatmap-grid {\n grid-template-columns: 36px repeat(24, 1fr);\n }\n\n .heatmap-cell {\n min-height: 22px;\n }\n\n .heatmap-cell__count {\n font-size: 8px;\n }\n\n .hourly-bars {\n height: 120px;\n }\n }\n "] }]
562
+ }], null, { TimeRange: [{
563
+ type: Input
564
+ }], TimeRangeChange: [{
565
+ type: Output
566
+ }] }); })();
567
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AnalyticsUsagePatternsComponent, { className: "AnalyticsUsagePatternsComponent", filePath: "src/AI/components/analytics/usage-patterns/usage-patterns.component.ts", lineNumber: 534 }); })();
568
+ export function LoadAnalyticsUsagePatterns() { }
569
+ //# sourceMappingURL=usage-patterns.component.js.map