@quicdata/analytics 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/analytics.css +69 -0
  2. package/core/auth.d.ts +19 -0
  3. package/core/auth.d.ts.map +1 -0
  4. package/core/auth.js +34 -0
  5. package/core/fetch-data.d.ts +115 -0
  6. package/core/fetch-data.d.ts.map +1 -0
  7. package/core/fetch-data.js +210 -0
  8. package/core/index.d.ts +8 -1
  9. package/core/index.d.ts.map +1 -1
  10. package/core/index.js +4 -3
  11. package/core/types.d.ts +250 -0
  12. package/core/types.d.ts.map +1 -0
  13. package/core/types.js +1 -0
  14. package/core/viewport-observer.d.ts +26 -0
  15. package/core/viewport-observer.d.ts.map +1 -0
  16. package/core/viewport-observer.js +38 -0
  17. package/core/widget-transform-runner.d.ts +22 -0
  18. package/core/widget-transform-runner.d.ts.map +1 -0
  19. package/core/widget-transform-runner.js +102 -0
  20. package/dashboard/dashboard-container.d.ts +100 -0
  21. package/dashboard/dashboard-container.d.ts.map +1 -0
  22. package/dashboard/dashboard-container.js +315 -0
  23. package/dashboard/index.d.ts +4 -0
  24. package/dashboard/index.d.ts.map +1 -0
  25. package/dashboard/index.js +2 -0
  26. package/designer/analytics-designer.d.ts +40 -0
  27. package/designer/analytics-designer.d.ts.map +1 -0
  28. package/designer/analytics-designer.js +267 -0
  29. package/designer/index.d.ts +5 -0
  30. package/designer/index.d.ts.map +1 -0
  31. package/designer/index.js +3 -0
  32. package/filters/filter-bar.d.ts +34 -0
  33. package/filters/filter-bar.d.ts.map +1 -0
  34. package/filters/filter-bar.js +233 -0
  35. package/filters/filter-button.d.ts +22 -0
  36. package/filters/filter-button.d.ts.map +1 -0
  37. package/filters/filter-button.js +86 -0
  38. package/filters/index.d.ts +7 -0
  39. package/filters/index.d.ts.map +1 -0
  40. package/filters/index.js +6 -0
  41. package/filters/widget-toolbar.d.ts +24 -0
  42. package/filters/widget-toolbar.d.ts.map +1 -0
  43. package/filters/widget-toolbar.js +219 -0
  44. package/index.d.ts +9 -1
  45. package/index.js +6 -1
  46. package/package.json +34 -9
  47. package/widgets/analytics-report.d.ts +49 -0
  48. package/widgets/analytics-report.d.ts.map +1 -0
  49. package/widgets/analytics-report.js +306 -0
  50. package/widgets/analytics-widget.d.ts +39 -0
  51. package/widgets/analytics-widget.d.ts.map +1 -0
  52. package/widgets/analytics-widget.js +230 -0
  53. package/widgets/bar-chart.d.ts +13 -0
  54. package/widgets/bar-chart.d.ts.map +1 -0
  55. package/widgets/bar-chart.js +77 -0
  56. package/widgets/base-chart.d.ts +92 -0
  57. package/widgets/base-chart.d.ts.map +1 -0
  58. package/widgets/base-chart.js +524 -0
  59. package/widgets/index.d.ts +13 -1
  60. package/widgets/index.d.ts.map +1 -1
  61. package/widgets/index.js +14 -3
  62. package/widgets/line-chart.d.ts +13 -0
  63. package/widgets/line-chart.d.ts.map +1 -0
  64. package/widgets/line-chart.js +71 -0
  65. package/widgets/pie-chart.d.ts +13 -0
  66. package/widgets/pie-chart.d.ts.map +1 -0
  67. package/widgets/pie-chart.js +55 -0
  68. package/widgets/table.d.ts +96 -0
  69. package/widgets/table.d.ts.map +1 -0
  70. package/widgets/table.js +721 -0
  71. package/index.d.ts.map +0 -1
@@ -0,0 +1,721 @@
1
+ var TableWidget_1;
2
+ import { __decorate } from "tslib";
3
+ import { LitElement, html, css, nothing } from 'lit';
4
+ import { customElement, property, state } from 'lit/decorators.js';
5
+ import { buildWidgetDataUrl, fetchWidgetData, fetchWidgetDefinition, } from '../core/fetch-data.js';
6
+ import { EVENT_DASHBOARD_FILTER_CHANGE, EVENT_DASHBOARD_REFRESH, } from '../dashboard/index.js';
7
+ import { observeViewport } from '../core/viewport-observer.js';
8
+ import '../filters/index.js';
9
+ /**
10
+ * Table widget. Fetches data from data-url (Laravel analytics API)
11
+ * or uses inline data, then renders an HTML table.
12
+ * When dashboard is set, merges dashboard params with dataParams and listens for
13
+ * analytics-dashboard-filter-change and analytics-dashboard-refresh.
14
+ */
15
+ let TableWidget = class TableWidget extends LitElement {
16
+ static { TableWidget_1 = this; }
17
+ static { this.styles = css `
18
+ :host {
19
+ display: flex;
20
+ flex-direction: column;
21
+ width: 100%;
22
+ height: 100%;
23
+ overflow: hidden;
24
+ }
25
+ table {
26
+ width: 100%;
27
+ border-collapse: collapse;
28
+ font-size: var(--table-font-size, 0.875rem);
29
+ }
30
+ th,
31
+ td {
32
+ padding: 0.5rem 0.75rem;
33
+ text-align: left;
34
+ border-bottom: 1px solid #e5e7eb;
35
+ }
36
+ th[rowspan],
37
+ td[rowspan] {
38
+ vertical-align: top;
39
+ }
40
+ th {
41
+ font-weight: 600;
42
+ color: #374151;
43
+ background: #f9fafb;
44
+ }
45
+ tr.totals-row td {
46
+ font-weight: 600;
47
+ background: #f3f4f6;
48
+ border-top: 2px solid #e5e7eb;
49
+ }
50
+ tr.totals-row.totals-row-sticky td {
51
+ position: sticky;
52
+ z-index: 1;
53
+ box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.06);
54
+ }
55
+ tr.totals-row.totals-row-sticky-bottom td {
56
+ bottom: 0;
57
+ }
58
+ tr.totals-row.totals-row-sticky-top td {
59
+ top: 0;
60
+ }
61
+ tr:hover td:not(.rowspan-placeholder) {
62
+ background: #f9fafb;
63
+ }
64
+ .error {
65
+ color: #c00;
66
+ padding: 0.5rem;
67
+ font-size: 0.875rem;
68
+ }
69
+ .loading-body {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ min-height: 80px;
74
+ padding: 1rem;
75
+ }
76
+ .loading-overlay {
77
+ position: absolute;
78
+ inset: 0;
79
+ z-index: 2;
80
+ display: flex;
81
+ align-items: center;
82
+ justify-content: center;
83
+ background: rgba(255, 255, 255, 0.65);
84
+ backdrop-filter: blur(4px);
85
+ -webkit-backdrop-filter: blur(4px);
86
+ border-radius: 4px;
87
+ }
88
+ .loading-spinner {
89
+ width: 2rem;
90
+ height: 2rem;
91
+ border: 2px solid #e5e7eb;
92
+ border-top-color: #3b82f6;
93
+ border-radius: 50%;
94
+ animation: analytics-spin 0.7s linear infinite;
95
+ }
96
+ @keyframes analytics-spin {
97
+ to { transform: rotate(360deg); }
98
+ }
99
+ .empty {
100
+ padding: 0.5rem;
101
+ color: #6b7280;
102
+ }
103
+ td.empty-placeholder {
104
+ text-align: center;
105
+ padding: 2rem 0.75rem;
106
+ color: #9ca3af;
107
+ }
108
+ .widget-with-toolbar {
109
+ position: relative;
110
+ width: 100%;
111
+ height: 100%;
112
+ display: flex;
113
+ flex-direction: column;
114
+ min-height: 0;
115
+ }
116
+ .widget-with-toolbar:hover analytics-widget-toolbar {
117
+ opacity: 1;
118
+ pointer-events: auto;
119
+ }
120
+ .widget-header {
121
+ flex-shrink: 0;
122
+ padding: 0.5rem 0.75rem 0.25rem 0.75rem;
123
+ min-width: 0;
124
+ }
125
+ .widget-title {
126
+ margin: 0;
127
+ font-size: 0.9375rem;
128
+ font-weight: 600;
129
+ color: #374151;
130
+ line-height: 1.3;
131
+ display: -webkit-box;
132
+ -webkit-box-orient: vertical;
133
+ -webkit-line-clamp: 1;
134
+ overflow: hidden;
135
+ text-overflow: ellipsis;
136
+ }
137
+ .widget-body {
138
+ flex: 1;
139
+ min-height: 0;
140
+ position: relative;
141
+ overflow: auto;
142
+ box-sizing: border-box;
143
+ }
144
+ `; }
145
+ constructor() {
146
+ super();
147
+ this._loadGeneration = 0;
148
+ this._loadDataDebounceId = null;
149
+ this._loadDataScheduled = false;
150
+ this._loadDataInFlight = false;
151
+ this._loadDataPendingRefresh = false;
152
+ this._dashboardEl = null;
153
+ this._ignoreNextFilterChange = false;
154
+ this._hasLoadedOnce = false;
155
+ this._viewportUnobserve = null;
156
+ this._boundOnDashboardFilterChange = (e) => {
157
+ const ce = e;
158
+ this._dashboardParams = { ...(ce.detail?.params ?? {}) };
159
+ this._loadData();
160
+ };
161
+ this._boundOnDashboardRefresh = () => {
162
+ if (this.lazy && !this._viewportVisible)
163
+ return;
164
+ this._loadData();
165
+ };
166
+ this.title = '';
167
+ this.dataUrl = '';
168
+ this.widgetId = '';
169
+ this.apiUrl = '';
170
+ this.dataParams = {};
171
+ this.dashboard = null;
172
+ this.data = null;
173
+ this.meta = { columns: {} };
174
+ this.lazy = false;
175
+ this.prefetchMargin = '200px';
176
+ this.initialDefinition = undefined;
177
+ this._widgetData = null;
178
+ this._loading = false;
179
+ this._error = null;
180
+ this._dashboardParams = {};
181
+ this._widgetFilters = [];
182
+ this._widgetFilterParams = {};
183
+ this._hideFilter = false;
184
+ this._viewportVisible = true;
185
+ this._definitionTitle = '';
186
+ }
187
+ connectedCallback() {
188
+ super.connectedCallback();
189
+ this._attachDashboard();
190
+ }
191
+ disconnectedCallback() {
192
+ this._viewportUnobserve?.();
193
+ this._viewportUnobserve = null;
194
+ if (this._loadDataDebounceId != null) {
195
+ clearTimeout(this._loadDataDebounceId);
196
+ this._loadDataDebounceId = null;
197
+ }
198
+ this._detachDashboard();
199
+ super.disconnectedCallback();
200
+ }
201
+ updated(changed) {
202
+ if (changed.has('dashboard')) {
203
+ this._detachDashboard();
204
+ this._attachDashboard();
205
+ }
206
+ if (changed.has('widgetId') || changed.has('apiUrl')) {
207
+ this._widgetFilters = [];
208
+ this._widgetFilterParams = {};
209
+ this._definitionTitle = '';
210
+ this._ignoreNextFilterChange = true;
211
+ }
212
+ const onlyFilterState = (changed.has('_widgetFilterParams') || changed.has('_widgetFilters')) &&
213
+ !changed.has('dataUrl') &&
214
+ !changed.has('dataParams') &&
215
+ !changed.has('widgetId') &&
216
+ !changed.has('apiUrl') &&
217
+ !changed.has('_dashboardParams');
218
+ if (onlyFilterState && this._ignoreNextFilterChange) {
219
+ // Skip scheduling: this update is from programmatic filter state (e.g. first load merge). Do not clear _ignoreNextFilterChange here; only _onWidgetFilterChange (user manual change) clears it.
220
+ }
221
+ else if (changed.has('dataUrl') ||
222
+ changed.has('dataParams') ||
223
+ changed.has('widgetId') ||
224
+ changed.has('apiUrl')) {
225
+ if (this._getEffectiveDataUrl() && !this._loadDataScheduled) {
226
+ this._loadDataScheduled = true;
227
+ queueMicrotask(() => {
228
+ this._loadDataScheduled = false;
229
+ this._loadData();
230
+ });
231
+ }
232
+ }
233
+ if (changed.has('lazy')) {
234
+ this._viewportVisible = !this.lazy;
235
+ }
236
+ }
237
+ _attachDashboard() {
238
+ const d = this.dashboard;
239
+ if (!d)
240
+ return;
241
+ this._dashboardEl = d;
242
+ this._dashboardParams = typeof d.getDashboardParams === 'function' ? d.getDashboardParams() : {};
243
+ d.addEventListener(EVENT_DASHBOARD_FILTER_CHANGE, this._boundOnDashboardFilterChange);
244
+ d.addEventListener(EVENT_DASHBOARD_REFRESH, this._boundOnDashboardRefresh);
245
+ }
246
+ _detachDashboard() {
247
+ const d = this._dashboardEl;
248
+ if (!d)
249
+ return;
250
+ d.removeEventListener(EVENT_DASHBOARD_FILTER_CHANGE, this._boundOnDashboardFilterChange);
251
+ d.removeEventListener(EVENT_DASHBOARD_REFRESH, this._boundOnDashboardRefresh);
252
+ this._dashboardEl = null;
253
+ this._dashboardParams = {};
254
+ }
255
+ firstUpdated() {
256
+ if (this.lazy) {
257
+ this._viewportVisible = false;
258
+ this._viewportUnobserve = observeViewport(this, {
259
+ rootMargin: this.prefetchMargin || '200px',
260
+ threshold: 0,
261
+ }, {
262
+ onEnter: () => {
263
+ this._viewportVisible = true;
264
+ if (!this._hasLoadedOnce) {
265
+ this._hasLoadedOnce = true;
266
+ this._loadData();
267
+ }
268
+ },
269
+ onLeave: () => {
270
+ this._viewportVisible = false;
271
+ },
272
+ });
273
+ }
274
+ }
275
+ /** Debounce (ms) so multiple updated() calls (e.g. dataUrl then dataParams in separate ticks) result in one fetch. */
276
+ static { this._LOAD_DATA_DEBOUNCE_MS = 50; }
277
+ /** Debounce data load so multiple updated() calls (e.g. dataUrl then dataParams) result in one fetch. */
278
+ _scheduleLoadData() {
279
+ if (this._loadDataDebounceId != null) {
280
+ clearTimeout(this._loadDataDebounceId);
281
+ this._loadDataDebounceId = null;
282
+ }
283
+ this._loadDataDebounceId = setTimeout(() => {
284
+ this._loadDataDebounceId = null;
285
+ this._loadData();
286
+ }, TableWidget_1._LOAD_DATA_DEBOUNCE_MS);
287
+ }
288
+ _getEffectiveDataUrl() {
289
+ if (this.apiUrl && this.widgetId) {
290
+ return buildWidgetDataUrl(this.apiUrl, this.widgetId);
291
+ }
292
+ return this.dataUrl;
293
+ }
294
+ _getEffectiveParams() {
295
+ const dashboardParams = this._dashboardParams;
296
+ const widgetParams = this.dataParams ?? {};
297
+ let widgetFilterParams = {};
298
+ if (this._hideFilter) {
299
+ for (const f of this._widgetFilters) {
300
+ if (f.default_value !== undefined && f.default_value !== null) {
301
+ widgetFilterParams[f.param_name] = f.default_value;
302
+ }
303
+ }
304
+ }
305
+ else if (this._widgetFilters.length > 0) {
306
+ for (const f of this._widgetFilters) {
307
+ const setByUser = f.param_name in this._widgetFilterParams;
308
+ widgetFilterParams[f.param_name] = setByUser
309
+ ? (this._widgetFilterParams[f.param_name] ?? '')
310
+ : (f.default_value ?? '');
311
+ }
312
+ }
313
+ else {
314
+ widgetFilterParams = { ...this._widgetFilterParams };
315
+ }
316
+ const out = { ...widgetFilterParams, ...widgetParams };
317
+ if (Object.keys(dashboardParams).length > 0) {
318
+ out.dashboard_params = dashboardParams;
319
+ }
320
+ return out;
321
+ }
322
+ /** Display title: optional host `title` overrides definition title from API. */
323
+ get _effectiveTitle() {
324
+ return (this.title != null && this.title !== '') ? this.title : (this._definitionTitle ?? '');
325
+ }
326
+ /** Run a callback after the current update cycle to avoid Lit "change-in-update" warning. */
327
+ _defer(fn) {
328
+ if (typeof requestAnimationFrame !== 'undefined') {
329
+ requestAnimationFrame(fn);
330
+ }
331
+ else {
332
+ setTimeout(fn, 0);
333
+ }
334
+ }
335
+ async _loadData() {
336
+ if (this.lazy && !this._viewportVisible)
337
+ return;
338
+ const dataUrl = this._getEffectiveDataUrl();
339
+ if (!dataUrl) {
340
+ this._error = null;
341
+ this._loading = false;
342
+ return;
343
+ }
344
+ if (this._loadDataInFlight) {
345
+ this._loadDataPendingRefresh = true;
346
+ return;
347
+ }
348
+ const gen = ++this._loadGeneration;
349
+ this._loadDataInFlight = true;
350
+ this._loading = true;
351
+ this._error = null;
352
+ try {
353
+ const useWidgetApi = Boolean(this.apiUrl && this.widgetId);
354
+ if (useWidgetApi) {
355
+ let params;
356
+ if (this._widgetFilters.length === 0) {
357
+ const def = this.initialDefinition ?? (await fetchWidgetDefinition(this.apiUrl, this.widgetId));
358
+ this._definitionTitle = def.title ?? '';
359
+ this._widgetFilters = def.filters ?? [];
360
+ this._hideFilter = def.hide_filter ?? false;
361
+ const defaultParams = {};
362
+ for (const f of (def.filters ?? [])) {
363
+ if (f.default_value !== undefined && f.default_value !== null) {
364
+ defaultParams[f.param_name] = f.default_value;
365
+ }
366
+ }
367
+ this._widgetFilterParams = { ...defaultParams, ...this._widgetFilterParams };
368
+ this._ignoreNextFilterChange = true;
369
+ params = { ...defaultParams, ...(this.dataParams ?? {}) };
370
+ if (Object.keys(this._dashboardParams).length > 0) {
371
+ params.dashboard_params = this._dashboardParams;
372
+ }
373
+ }
374
+ else {
375
+ params = this._getEffectiveParams();
376
+ }
377
+ const res = await fetchWidgetData(dataUrl, params);
378
+ if (gen !== this._loadGeneration)
379
+ return;
380
+ const data = res.data ?? [];
381
+ const meta = res.meta ?? { columns: {} };
382
+ const wd = res.widgetData;
383
+ const widgetData = wd && wd.chartType === 'Table'
384
+ ? {
385
+ chartType: 'Table',
386
+ columns: wd.columns ?? [],
387
+ columnDefs: wd.columnDefs,
388
+ header_rows: wd.header_rows,
389
+ rows: wd.rows ?? res.data ?? [],
390
+ rowSpanRuns: wd.rowSpanRuns,
391
+ font_size: wd.font_size,
392
+ totals_sticky: wd.totals_sticky,
393
+ totals_position: wd.totals_position,
394
+ }
395
+ : null;
396
+ const filters = res.filters;
397
+ const hideFilter = res.hide_filter;
398
+ this._defer(() => {
399
+ if (gen !== this._loadGeneration)
400
+ return;
401
+ this._loadDataInFlight = false;
402
+ this.data = data;
403
+ this.meta = meta;
404
+ this._widgetData = widgetData;
405
+ if (filters?.length)
406
+ this._widgetFilters = filters;
407
+ else if (hideFilter !== true)
408
+ this._widgetFilters = [];
409
+ if (hideFilter !== undefined)
410
+ this._hideFilter = hideFilter;
411
+ this._loading = false;
412
+ if (this._loadDataPendingRefresh) {
413
+ this._loadDataPendingRefresh = false;
414
+ this._scheduleLoadData();
415
+ }
416
+ });
417
+ }
418
+ else {
419
+ const params = this._getEffectiveParams();
420
+ const res = await fetchWidgetData(dataUrl, params);
421
+ if (gen !== this._loadGeneration)
422
+ return;
423
+ const data = res.data ?? [];
424
+ const meta = res.meta ?? { columns: {} };
425
+ const wd = res.widgetData;
426
+ const widgetData = wd && wd.chartType === 'Table'
427
+ ? {
428
+ chartType: 'Table',
429
+ columns: wd.columns ?? [],
430
+ columnDefs: wd.columnDefs,
431
+ header_rows: wd.header_rows,
432
+ rows: wd.rows ?? res.data ?? [],
433
+ rowSpanRuns: wd.rowSpanRuns,
434
+ font_size: wd.font_size,
435
+ totals_sticky: wd.totals_sticky,
436
+ totals_position: wd.totals_position,
437
+ }
438
+ : null;
439
+ const filters = res.filters;
440
+ const hideFilter = res.hide_filter;
441
+ this._defer(() => {
442
+ if (gen !== this._loadGeneration)
443
+ return;
444
+ this._loadDataInFlight = false;
445
+ this.data = data;
446
+ this.meta = meta;
447
+ this._widgetData = widgetData;
448
+ if (filters?.length)
449
+ this._widgetFilters = filters;
450
+ else if (hideFilter !== true)
451
+ this._widgetFilters = [];
452
+ if (hideFilter !== undefined)
453
+ this._hideFilter = hideFilter;
454
+ this._loading = false;
455
+ if (this._loadDataPendingRefresh) {
456
+ this._loadDataPendingRefresh = false;
457
+ this._scheduleLoadData();
458
+ }
459
+ });
460
+ }
461
+ }
462
+ catch (e) {
463
+ const errMsg = e instanceof Error ? e.message : String(e);
464
+ this._defer(() => {
465
+ if (gen !== this._loadGeneration)
466
+ return;
467
+ this._loadDataInFlight = false;
468
+ this._error = errMsg;
469
+ this.data = [];
470
+ this._widgetData = null;
471
+ this._loading = false;
472
+ if (this._loadDataPendingRefresh) {
473
+ this._loadDataPendingRefresh = false;
474
+ this._scheduleLoadData();
475
+ }
476
+ });
477
+ }
478
+ }
479
+ _onWidgetFilterChange(e) {
480
+ this._ignoreNextFilterChange = false;
481
+ this._widgetFilterParams = { ...(e.detail?.params ?? {}) };
482
+ this._scheduleLoadData();
483
+ }
484
+ /** Column keys in display order. From widgetData when present, else derived from data/meta. */
485
+ _getColumns() {
486
+ const wd = this._widgetData;
487
+ if (wd?.columns?.length)
488
+ return wd.columns;
489
+ const rows = this.data ?? [];
490
+ const metaKeys = Object.keys(this.meta?.columns ?? {});
491
+ if (rows.length > 0)
492
+ return Object.keys(rows[0]);
493
+ return metaKeys.length > 0 ? metaKeys : [];
494
+ }
495
+ /** Body rows. From widgetData.rows when present, else from data. */
496
+ _getRows() {
497
+ const wd = this._widgetData;
498
+ if (wd?.rows?.length !== undefined && wd.rows.length >= 0)
499
+ return wd.rows;
500
+ return this.data ?? [];
501
+ }
502
+ /** Header label for column key. From widgetData.columnDefs or header_rows when present. */
503
+ _getColumnLabel(colKey) {
504
+ const wd = this._widgetData;
505
+ if (wd?.columnDefs?.length) {
506
+ const def = wd.columnDefs.find((c) => c.key === colKey);
507
+ if (def)
508
+ return def.label;
509
+ }
510
+ return colKey;
511
+ }
512
+ /** Text-align for column. From widgetData.columnDefs when present. */
513
+ _getColumnAlign(colKey) {
514
+ const wd = this._widgetData;
515
+ if (wd?.columnDefs?.length) {
516
+ const def = wd.columnDefs.find((c) => c.key === colKey);
517
+ if (def?.align)
518
+ return def.align;
519
+ }
520
+ return null;
521
+ }
522
+ /** Rowspan for this cell (column, rowIndex). 0 = merged (skip rendering cell). */
523
+ _getCellRowSpan(colKey, rowIndex) {
524
+ const wd = this._widgetData;
525
+ const runs = wd?.rowSpanRuns?.[colKey];
526
+ if (!runs || rowIndex >= runs.length)
527
+ return 1;
528
+ return runs[rowIndex] ?? 1;
529
+ }
530
+ _isTotalsRow(row) {
531
+ return Boolean(row['_is_totals_row']);
532
+ }
533
+ render() {
534
+ const hasWidgetFilters = this._widgetFilters.length > 0 && !this._hideFilter;
535
+ const widgetActiveCount = Object.keys(this._widgetFilterParams).filter((k) => this._widgetFilterParams[k] !== '' && this._widgetFilterParams[k] != null).length;
536
+ const rows = this._getRows();
537
+ const columns = this._getColumns();
538
+ const wd = this._widgetData;
539
+ const hasHeaderRows = Boolean(wd?.header_rows?.length);
540
+ const tableFontSizeStyle = wd?.font_size ? `font-size: ${wd.font_size}` : nothing;
541
+ const filterToolbar = hasWidgetFilters
542
+ ? html `
543
+ <analytics-widget-toolbar
544
+ .filters=${this._widgetFilters}
545
+ .values=${this._widgetFilterParams}
546
+ .activeCount=${widgetActiveCount}
547
+ @analytics-filter-change=${this._onWidgetFilterChange}
548
+ ></analytics-widget-toolbar>
549
+ `
550
+ : html ``;
551
+ const bodyContent = this._error
552
+ ? html `<div class="error">${this._error}</div>`
553
+ : columns.length === 0
554
+ ? html `<div class="empty">No columns</div>`
555
+ : rows.length === 0
556
+ ? html `
557
+ <table style=${tableFontSizeStyle}>
558
+ <thead>
559
+ ${this._renderThead(columns, hasHeaderRows)}
560
+ </thead>
561
+ <tbody>
562
+ <tr>
563
+ <td colspan=${columns.length} class="empty-placeholder">No data</td>
564
+ </tr>
565
+ </tbody>
566
+ </table>
567
+ `
568
+ : html `
569
+ <table style=${tableFontSizeStyle}>
570
+ <thead>
571
+ ${this._renderThead(columns, hasHeaderRows)}
572
+ </thead>
573
+ <tbody>
574
+ ${rows.map((row, rowIndex) => this._renderBodyRow(row, rowIndex, columns))}
575
+ </tbody>
576
+ </table>
577
+ `;
578
+ return html `
579
+ <div class="widget-with-toolbar">
580
+ ${this._effectiveTitle
581
+ ? html `<header class="widget-header"><h2 class="widget-title">${this._effectiveTitle}</h2></header>`
582
+ : ''}
583
+ <div class="widget-body">
584
+ ${bodyContent}
585
+ ${this._loading
586
+ ? html `<div class="loading-overlay" aria-busy="true" aria-live="polite">
587
+ <div class="loading-spinner" aria-hidden="true"></div>
588
+ </div>`
589
+ : nothing}
590
+ </div>
591
+ ${filterToolbar}
592
+ </div>
593
+ `;
594
+ }
595
+ _renderThead(columns, useHeaderRows) {
596
+ const wd = this._widgetData;
597
+ if (useHeaderRows && wd?.header_rows?.length) {
598
+ return html `${wd.header_rows.map((headerRow) => html `
599
+ <tr>
600
+ ${headerRow.map((cell) => {
601
+ if (cell.hide)
602
+ return null;
603
+ return html `<th
604
+ colspan=${cell.colspan != null && cell.colspan > 1 ? cell.colspan : nothing}
605
+ rowspan=${cell.rowspan != null && cell.rowspan > 1 ? cell.rowspan : nothing}
606
+ >${cell.label}</th>`;
607
+ })}
608
+ </tr>
609
+ `)}`;
610
+ }
611
+ return html `
612
+ <tr>
613
+ ${columns.map((col) => {
614
+ const align = this._getColumnAlign(col);
615
+ const style = align ? `text-align: ${align}` : undefined;
616
+ return html `<th style=${style ?? nothing}>${this._getColumnLabel(col)}</th>`;
617
+ })}
618
+ </tr>
619
+ `;
620
+ }
621
+ _renderBodyRow(row, rowIndex, columns) {
622
+ const isTotals = this._isTotalsRow(row);
623
+ const wd = this._widgetData;
624
+ const sticky = Boolean(isTotals && wd?.totals_sticky);
625
+ const stickyClass = sticky
626
+ ? wd?.totals_position === 'top'
627
+ ? 'totals-row-sticky totals-row-sticky-top'
628
+ : 'totals-row-sticky totals-row-sticky-bottom'
629
+ : '';
630
+ const totalsLabel = isTotals && row['_totals_label'] != null ? String(row['_totals_label']) : null;
631
+ return html `
632
+ <tr class=${isTotals ? `totals-row ${stickyClass}` : ''}>
633
+ ${columns.map((col, colIndex) => {
634
+ const rowSpan = this._getCellRowSpan(col, rowIndex);
635
+ if (rowSpan === 0)
636
+ return null;
637
+ const displayValue = colIndex === 0 && totalsLabel != null ? totalsLabel : row[col];
638
+ const align = this._getColumnAlign(col);
639
+ const style = align ? `text-align: ${align}` : undefined;
640
+ const cellContent = this._formatCell(displayValue, col);
641
+ return html `<td
642
+ style=${style ?? nothing}
643
+ rowspan=${rowSpan > 1 ? rowSpan : nothing}
644
+ class=${rowSpan === 0 ? 'rowspan-placeholder' : ''}
645
+ >${cellContent}</td>`;
646
+ })}
647
+ </tr>
648
+ `;
649
+ }
650
+ _formatCell(value, _col) {
651
+ if (value == null)
652
+ return '';
653
+ if (typeof value === 'object')
654
+ return JSON.stringify(value);
655
+ return value;
656
+ }
657
+ };
658
+ __decorate([
659
+ property({ type: String })
660
+ ], TableWidget.prototype, "title", void 0);
661
+ __decorate([
662
+ property({ type: String, attribute: 'data-url' })
663
+ ], TableWidget.prototype, "dataUrl", void 0);
664
+ __decorate([
665
+ property({ type: String, attribute: 'widget-id' })
666
+ ], TableWidget.prototype, "widgetId", void 0);
667
+ __decorate([
668
+ property({ type: String, attribute: 'api-url' })
669
+ ], TableWidget.prototype, "apiUrl", void 0);
670
+ __decorate([
671
+ property({ type: Object })
672
+ ], TableWidget.prototype, "dataParams", void 0);
673
+ __decorate([
674
+ property({ type: Object })
675
+ ], TableWidget.prototype, "dashboard", void 0);
676
+ __decorate([
677
+ property({ type: Object })
678
+ ], TableWidget.prototype, "data", void 0);
679
+ __decorate([
680
+ property({ type: Object })
681
+ ], TableWidget.prototype, "meta", void 0);
682
+ __decorate([
683
+ state()
684
+ ], TableWidget.prototype, "_widgetData", void 0);
685
+ __decorate([
686
+ property({ type: Boolean })
687
+ ], TableWidget.prototype, "lazy", void 0);
688
+ __decorate([
689
+ property({ type: String, attribute: 'prefetch-margin' })
690
+ ], TableWidget.prototype, "prefetchMargin", void 0);
691
+ __decorate([
692
+ property({ type: Object })
693
+ ], TableWidget.prototype, "initialDefinition", void 0);
694
+ __decorate([
695
+ state()
696
+ ], TableWidget.prototype, "_loading", void 0);
697
+ __decorate([
698
+ state()
699
+ ], TableWidget.prototype, "_error", void 0);
700
+ __decorate([
701
+ state()
702
+ ], TableWidget.prototype, "_dashboardParams", void 0);
703
+ __decorate([
704
+ state()
705
+ ], TableWidget.prototype, "_widgetFilters", void 0);
706
+ __decorate([
707
+ state()
708
+ ], TableWidget.prototype, "_widgetFilterParams", void 0);
709
+ __decorate([
710
+ state()
711
+ ], TableWidget.prototype, "_hideFilter", void 0);
712
+ __decorate([
713
+ state()
714
+ ], TableWidget.prototype, "_viewportVisible", void 0);
715
+ __decorate([
716
+ state()
717
+ ], TableWidget.prototype, "_definitionTitle", void 0);
718
+ TableWidget = TableWidget_1 = __decorate([
719
+ customElement('analytics-table')
720
+ ], TableWidget);
721
+ export { TableWidget };