@quicdata/analytics 0.0.8 → 0.0.10
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/package.json +1 -1
- package/widgets/analytics-widget.d.ts +1 -0
- package/widgets/analytics-widget.d.ts.map +1 -1
- package/widgets/analytics-widget.js +13 -1
- package/widgets/base-chart.d.ts.map +1 -1
- package/widgets/base-chart.js +186 -14
- package/widgets/index.d.ts +2 -0
- package/widgets/index.d.ts.map +1 -1
- package/widgets/index.js +2 -0
- package/widgets/number-kpi.d.ts +52 -0
- package/widgets/number-kpi.d.ts.map +1 -0
- package/widgets/number-kpi.js +569 -0
- package/widgets/table.d.ts.map +1 -1
- package/widgets/table.js +110 -18
package/package.json
CHANGED
|
@@ -4,6 +4,7 @@ import './bar-chart.js';
|
|
|
4
4
|
import './line-chart.js';
|
|
5
5
|
import './pie-chart.js';
|
|
6
6
|
import './table.js';
|
|
7
|
+
import './number-kpi.js';
|
|
7
8
|
/**
|
|
8
9
|
* Generic analytics widget: pass widgetId + apiUrl (and optional config);
|
|
9
10
|
* fetches the widget definition and renders the appropriate chart/table internally.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics-widget.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/analytics-widget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAIrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAE9E,OAAO,gBAAgB,CAAC;AACxB,OAAO,iBAAiB,CAAC;AACzB,OAAO,gBAAgB,CAAC;AACxB,OAAO,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"analytics-widget.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/analytics-widget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAIrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAE9E,OAAO,gBAAgB,CAAC;AACxB,OAAO,iBAAiB,CAAC;AACzB,OAAO,gBAAgB,CAAC;AACxB,OAAO,YAAY,CAAC;AACpB,OAAO,iBAAiB,CAAC;AAazB;;;;GAIG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,OAAgB,MAAM,0BAmCpB;IAEF,sGAAsG;IAC1C,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtF,2FAA2F;IACjC,MAAM,EAAE,MAAM,CAAC;IAEzE,gGAAgG;IAC5D,KAAK,EAAE,MAAM,CAAC;IAElD,gEAAgE;IAC5B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAE1F,2EAA2E;IACvC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAEzE,4EAA4E;IACvC,IAAI,EAAE,OAAO,CAAC;IAEnD,yEAAyE;IACP,cAAc,EAAE,MAAM,CAAC;IAEzF,QAAyB,UAAU,CAAuB;IAC1D,QAAyB,WAAW,CAAgC;IACpE,QAAyB,QAAQ,CAAU;IAC3C,QAAyB,MAAM,CAAgB;;IAiBtC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;YAWvC,eAAe;IAqB7B,OAAO,KAAK,SAAS,GAGpB;IAED,OAAO,CAAC,kBAAkB;IAyEjB,MAAM;CAgBhB"}
|
|
@@ -7,6 +7,7 @@ import './bar-chart.js';
|
|
|
7
7
|
import './line-chart.js';
|
|
8
8
|
import './pie-chart.js';
|
|
9
9
|
import './table.js';
|
|
10
|
+
import './number-kpi.js';
|
|
10
11
|
/** Map API chart_type to inner widget type. */
|
|
11
12
|
const CHART_TYPE_TO_TAG = {
|
|
12
13
|
Bar: 'analytics-bar-chart',
|
|
@@ -14,7 +15,7 @@ const CHART_TYPE_TO_TAG = {
|
|
|
14
15
|
Line: 'analytics-line-chart',
|
|
15
16
|
Pie: 'analytics-pie-chart',
|
|
16
17
|
Donut: 'analytics-pie-chart',
|
|
17
|
-
Number: 'analytics-
|
|
18
|
+
Number: 'analytics-number-kpi',
|
|
18
19
|
Table: 'analytics-table',
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
@@ -159,6 +160,17 @@ let AnalyticsWidget = class AnalyticsWidget extends LitElement {
|
|
|
159
160
|
?lazy=${common.lazy}
|
|
160
161
|
prefetch-margin=${common.prefetchMargin}
|
|
161
162
|
></analytics-pie-chart>`;
|
|
163
|
+
case 'analytics-number-kpi':
|
|
164
|
+
return html `<analytics-number-kpi
|
|
165
|
+
widget-id=${common.widgetId}
|
|
166
|
+
api-url=${common.apiUrl}
|
|
167
|
+
.title=${common.title}
|
|
168
|
+
.dataParams=${common.dataParams}
|
|
169
|
+
.dashboard=${common.dashboard}
|
|
170
|
+
.initialDefinition=${common.initialDefinition}
|
|
171
|
+
?lazy=${common.lazy}
|
|
172
|
+
prefetch-margin=${common.prefetchMargin}
|
|
173
|
+
></analytics-number-kpi>`;
|
|
162
174
|
case 'analytics-table':
|
|
163
175
|
return html `<analytics-table
|
|
164
176
|
widget-id=${common.widgetId}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-chart.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/base-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,KAAK,EAA0B,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAO5F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAM9E,OAAO,qBAAqB,CAAC;AAE7B;;;;;GAKG;AACH,8BAAsB,eAAgB,SAAQ,UAAU;IACtD,OAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"base-chart.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/base-chart.ts"],"names":[],"mappings":"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"}
|
package/widgets/base-chart.js
CHANGED
|
@@ -41,15 +41,122 @@ export class BaseChartWidget extends LitElement {
|
|
|
41
41
|
.loading-overlay {
|
|
42
42
|
position: absolute;
|
|
43
43
|
inset: 0;
|
|
44
|
-
z-index:
|
|
44
|
+
z-index: 10;
|
|
45
45
|
display: flex;
|
|
46
46
|
align-items: center;
|
|
47
47
|
justify-content: center;
|
|
48
|
-
background: rgba(255, 255, 255, 0.
|
|
48
|
+
background: rgba(255, 255, 255, 0.8);
|
|
49
49
|
backdrop-filter: blur(4px);
|
|
50
50
|
-webkit-backdrop-filter: blur(4px);
|
|
51
51
|
border-radius: 4px;
|
|
52
52
|
}
|
|
53
|
+
.skeleton-line-chart {
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: 100%;
|
|
56
|
+
min-height: 120px;
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
}
|
|
63
|
+
.skeleton-line-chart-canvas {
|
|
64
|
+
width: 70%;
|
|
65
|
+
max-width: 180px;
|
|
66
|
+
aspect-ratio: 1;
|
|
67
|
+
min-width: 0;
|
|
68
|
+
}
|
|
69
|
+
.skeleton-line-chart-canvas svg {
|
|
70
|
+
width: 100%;
|
|
71
|
+
height: 100%;
|
|
72
|
+
display: block;
|
|
73
|
+
}
|
|
74
|
+
.skeleton-line-chart .zigzag {
|
|
75
|
+
fill: none;
|
|
76
|
+
stroke: #e5e7eb;
|
|
77
|
+
stroke-width: 2.5;
|
|
78
|
+
stroke-linecap: round;
|
|
79
|
+
stroke-linejoin: round;
|
|
80
|
+
}
|
|
81
|
+
.skeleton-line-chart .zigzag-shine {
|
|
82
|
+
fill: none;
|
|
83
|
+
stroke: url(#skeleton-line-gradient);
|
|
84
|
+
stroke-width: 2.5;
|
|
85
|
+
stroke-linecap: round;
|
|
86
|
+
stroke-linejoin: round;
|
|
87
|
+
stroke-dasharray: 15 85;
|
|
88
|
+
animation: analytics-line-shimmer 1.2s ease-in-out infinite;
|
|
89
|
+
}
|
|
90
|
+
@keyframes analytics-line-shimmer {
|
|
91
|
+
to { stroke-dashoffset: -100; }
|
|
92
|
+
}
|
|
93
|
+
.skeleton-bar-chart {
|
|
94
|
+
width: 100%;
|
|
95
|
+
height: 100%;
|
|
96
|
+
min-height: 120px;
|
|
97
|
+
display: flex;
|
|
98
|
+
align-items: center;
|
|
99
|
+
justify-content: center;
|
|
100
|
+
padding: 1rem;
|
|
101
|
+
box-sizing: border-box;
|
|
102
|
+
}
|
|
103
|
+
.skeleton-bar-chart-canvas {
|
|
104
|
+
width: 70%;
|
|
105
|
+
max-width: 180px;
|
|
106
|
+
aspect-ratio: 1;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: flex-end;
|
|
109
|
+
gap: 1rem;
|
|
110
|
+
min-width: 0;
|
|
111
|
+
}
|
|
112
|
+
.skeleton-bar-vert {
|
|
113
|
+
flex: 1;
|
|
114
|
+
min-width: 0;
|
|
115
|
+
border-radius: 4px 4px 0 0;
|
|
116
|
+
background: linear-gradient(
|
|
117
|
+
90deg,
|
|
118
|
+
#e5e7eb 0%,
|
|
119
|
+
#e5e7eb 40%,
|
|
120
|
+
#f3f4f6 50%,
|
|
121
|
+
#e5e7eb 60%,
|
|
122
|
+
#e5e7eb 100%
|
|
123
|
+
);
|
|
124
|
+
background-size: 200% 100%;
|
|
125
|
+
animation: analytics-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
126
|
+
}
|
|
127
|
+
.skeleton-bar-vert:nth-child(1) { height: 55%; }
|
|
128
|
+
.skeleton-bar-vert:nth-child(2) { height: 70%; }
|
|
129
|
+
.skeleton-bar-vert:nth-child(3) { height: 60%; }
|
|
130
|
+
.skeleton-bar-vert:nth-child(4) { height: 80%; }
|
|
131
|
+
.skeleton-pie {
|
|
132
|
+
width: 100%;
|
|
133
|
+
height: 100%;
|
|
134
|
+
min-height: 120px;
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
padding: 1rem;
|
|
139
|
+
box-sizing: border-box;
|
|
140
|
+
}
|
|
141
|
+
.skeleton-circle {
|
|
142
|
+
width: 70%;
|
|
143
|
+
max-width: 180px;
|
|
144
|
+
aspect-ratio: 1;
|
|
145
|
+
border-radius: 50%;
|
|
146
|
+
background: linear-gradient(
|
|
147
|
+
90deg,
|
|
148
|
+
#e5e7eb 0%,
|
|
149
|
+
#e5e7eb 40%,
|
|
150
|
+
#f3f4f6 50%,
|
|
151
|
+
#e5e7eb 60%,
|
|
152
|
+
#e5e7eb 100%
|
|
153
|
+
);
|
|
154
|
+
background-size: 200% 100%;
|
|
155
|
+
animation: analytics-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
156
|
+
}
|
|
157
|
+
@keyframes analytics-skeleton-shimmer {
|
|
158
|
+
to { background-position: 200% 0; }
|
|
159
|
+
}
|
|
53
160
|
.loading-spinner {
|
|
54
161
|
width: 2rem;
|
|
55
162
|
height: 2rem;
|
|
@@ -97,6 +204,22 @@ export class BaseChartWidget extends LitElement {
|
|
|
97
204
|
overflow: hidden;
|
|
98
205
|
text-overflow: ellipsis;
|
|
99
206
|
}
|
|
207
|
+
.skeleton-title {
|
|
208
|
+
height: 1rem;
|
|
209
|
+
width: 8rem;
|
|
210
|
+
max-width: 60%;
|
|
211
|
+
border-radius: 4px;
|
|
212
|
+
background: linear-gradient(
|
|
213
|
+
90deg,
|
|
214
|
+
#e5e7eb 0%,
|
|
215
|
+
#e5e7eb 40%,
|
|
216
|
+
#f3f4f6 50%,
|
|
217
|
+
#e5e7eb 60%,
|
|
218
|
+
#e5e7eb 100%
|
|
219
|
+
);
|
|
220
|
+
background-size: 200% 100%;
|
|
221
|
+
animation: analytics-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
222
|
+
}
|
|
100
223
|
.widget-body {
|
|
101
224
|
flex: 1;
|
|
102
225
|
min-height: 0;
|
|
@@ -275,10 +398,8 @@ export class BaseChartWidget extends LitElement {
|
|
|
275
398
|
this._viewportUnobserve = observeViewport(this, { rootMargin: this.prefetchMargin || '200px', threshold: 0 }, {
|
|
276
399
|
onEnter: () => {
|
|
277
400
|
this._viewportVisible = true;
|
|
278
|
-
if (!this._hasLoadedOnce)
|
|
279
|
-
this._hasLoadedOnce = true;
|
|
401
|
+
if (!this._hasLoadedOnce)
|
|
280
402
|
this._loadData();
|
|
281
|
-
}
|
|
282
403
|
},
|
|
283
404
|
onLeave: () => {
|
|
284
405
|
this._viewportVisible = false;
|
|
@@ -389,6 +510,7 @@ export class BaseChartWidget extends LitElement {
|
|
|
389
510
|
this._widgetFilters = filters;
|
|
390
511
|
if (hideFilter !== undefined)
|
|
391
512
|
this._hideFilter = hideFilter;
|
|
513
|
+
this._hasLoadedOnce = true;
|
|
392
514
|
this._loading = false;
|
|
393
515
|
this._renderChart();
|
|
394
516
|
});
|
|
@@ -406,6 +528,7 @@ export class BaseChartWidget extends LitElement {
|
|
|
406
528
|
this.data = data;
|
|
407
529
|
this.meta = meta;
|
|
408
530
|
this._widgetData = null;
|
|
531
|
+
this._hasLoadedOnce = true;
|
|
409
532
|
this._loading = false;
|
|
410
533
|
this._renderChart();
|
|
411
534
|
});
|
|
@@ -470,10 +593,16 @@ export class BaseChartWidget extends LitElement {
|
|
|
470
593
|
const hasWidgetFilters = this._widgetFilters.length > 0 && !this._hideFilter;
|
|
471
594
|
const widgetActiveCount = Object.keys(this._widgetFilterParams).filter((k) => this._widgetFilterParams[k] !== '' && this._widgetFilterParams[k] != null).length;
|
|
472
595
|
const canRefresh = Boolean(this._getEffectiveDataUrl());
|
|
473
|
-
const
|
|
596
|
+
const showSkeleton = this._loading && !this._hasLoadedOnce;
|
|
597
|
+
const showTitlePlaceholder = !this._effectiveTitle;
|
|
598
|
+
const headerContent = this._effectiveTitle || canRefresh || showTitlePlaceholder
|
|
474
599
|
? html `
|
|
475
600
|
<header class="widget-header">
|
|
476
|
-
${this._effectiveTitle
|
|
601
|
+
${this._effectiveTitle
|
|
602
|
+
? html `<h2 class="widget-title">${this._effectiveTitle}</h2>`
|
|
603
|
+
: showTitlePlaceholder
|
|
604
|
+
? html `<div class="skeleton-title" aria-hidden="true"></div>`
|
|
605
|
+
: html `<span></span>`}
|
|
477
606
|
${canRefresh
|
|
478
607
|
? html `
|
|
479
608
|
<button
|
|
@@ -496,19 +625,62 @@ export class BaseChartWidget extends LitElement {
|
|
|
496
625
|
</header>
|
|
497
626
|
`
|
|
498
627
|
: '';
|
|
628
|
+
const showSpinnerOverlay = this._loading && this._hasLoadedOnce;
|
|
629
|
+
const isBarChart = this.tagName === 'ANALYTICS-BAR-CHART';
|
|
630
|
+
const isPieChart = this.tagName === 'ANALYTICS-PIE-CHART';
|
|
631
|
+
const skeletonContent = showSkeleton
|
|
632
|
+
? isBarChart
|
|
633
|
+
? html `
|
|
634
|
+
<div class="skeleton-bar-chart" aria-busy="true" aria-live="polite">
|
|
635
|
+
<div class="skeleton-bar-chart-canvas">
|
|
636
|
+
<div class="skeleton-bar-vert"></div>
|
|
637
|
+
<div class="skeleton-bar-vert"></div>
|
|
638
|
+
<div class="skeleton-bar-vert"></div>
|
|
639
|
+
<div class="skeleton-bar-vert"></div>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
`
|
|
643
|
+
: isPieChart
|
|
644
|
+
? html `
|
|
645
|
+
<div class="skeleton-pie" aria-busy="true" aria-live="polite">
|
|
646
|
+
<div class="skeleton-circle"></div>
|
|
647
|
+
</div>
|
|
648
|
+
`
|
|
649
|
+
: html `
|
|
650
|
+
<div class="skeleton-line-chart" aria-busy="true" aria-live="polite">
|
|
651
|
+
<div class="skeleton-line-chart-canvas">
|
|
652
|
+
<svg viewBox="0 0 100 100" preserveAspectRatio="none">
|
|
653
|
+
<defs>
|
|
654
|
+
<linearGradient id="skeleton-line-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
655
|
+
<stop offset="0%" stop-color="#e5e7eb"/>
|
|
656
|
+
<stop offset="40%" stop-color="#e5e7eb"/>
|
|
657
|
+
<stop offset="50%" stop-color="#f3f4f6"/>
|
|
658
|
+
<stop offset="60%" stop-color="#e5e7eb"/>
|
|
659
|
+
<stop offset="100%" stop-color="#e5e7eb"/>
|
|
660
|
+
</linearGradient>
|
|
661
|
+
</defs>
|
|
662
|
+
<polyline class="zigzag" points="8,80 23,29 38,33 53,37 68,90 83,82 95,10"/>
|
|
663
|
+
<polyline class="zigzag zigzag-shine" points="8,80 23,29 38,33 53,37 68,90 83,82 95,10"/>
|
|
664
|
+
</svg>
|
|
665
|
+
</div>
|
|
666
|
+
</div>
|
|
667
|
+
`
|
|
668
|
+
: null;
|
|
499
669
|
return html `
|
|
500
670
|
<div class="widget-with-toolbar">
|
|
501
671
|
${headerContent}
|
|
502
672
|
<div class="widget-body">
|
|
503
|
-
${
|
|
504
|
-
?
|
|
505
|
-
:
|
|
506
|
-
|
|
673
|
+
${skeletonContent !== null
|
|
674
|
+
? skeletonContent
|
|
675
|
+
: this._error
|
|
676
|
+
? html `<div class="error">${this._error}</div>`
|
|
677
|
+
: html `<div class="chart"></div>`}
|
|
678
|
+
</div>
|
|
679
|
+
${showSpinnerOverlay
|
|
507
680
|
? html `<div class="loading-overlay" aria-busy="true" aria-live="polite">
|
|
508
|
-
|
|
509
|
-
|
|
681
|
+
<div class="loading-spinner" aria-hidden="true"></div>
|
|
682
|
+
</div>`
|
|
510
683
|
: nothing}
|
|
511
|
-
</div>
|
|
512
684
|
${hasWidgetFilters
|
|
513
685
|
? html `
|
|
514
686
|
<analytics-widget-toolbar
|
package/widgets/index.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ export { BarChartWidget } from './bar-chart.js';
|
|
|
3
3
|
export { LineChartWidget } from './line-chart.js';
|
|
4
4
|
export { PieChartWidget } from './pie-chart.js';
|
|
5
5
|
export { TableWidget } from './table.js';
|
|
6
|
+
export { NumberKpiWidget } from './number-kpi.js';
|
|
6
7
|
export { AnalyticsWidget } from './analytics-widget.js';
|
|
7
8
|
export { AnalyticsReport } from './analytics-report.js';
|
|
8
9
|
import './bar-chart.js';
|
|
9
10
|
import './line-chart.js';
|
|
10
11
|
import './pie-chart.js';
|
|
11
12
|
import './table.js';
|
|
13
|
+
import './number-kpi.js';
|
|
12
14
|
import './analytics-widget.js';
|
|
13
15
|
import './analytics-report.js';
|
|
14
16
|
//# sourceMappingURL=index.d.ts.map
|
package/widgets/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,gBAAgB,CAAC;AACxB,OAAO,iBAAiB,CAAC;AACzB,OAAO,gBAAgB,CAAC;AACxB,OAAO,YAAY,CAAC;AACpB,OAAO,uBAAuB,CAAC;AAC/B,OAAO,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,gBAAgB,CAAC;AACxB,OAAO,iBAAiB,CAAC;AACzB,OAAO,gBAAgB,CAAC;AACxB,OAAO,YAAY,CAAC;AACpB,OAAO,iBAAiB,CAAC;AACzB,OAAO,uBAAuB,CAAC;AAC/B,OAAO,uBAAuB,CAAC"}
|
package/widgets/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { BarChartWidget } from './bar-chart.js';
|
|
|
3
3
|
export { LineChartWidget } from './line-chart.js';
|
|
4
4
|
export { PieChartWidget } from './pie-chart.js';
|
|
5
5
|
export { TableWidget } from './table.js';
|
|
6
|
+
export { NumberKpiWidget } from './number-kpi.js';
|
|
6
7
|
export { AnalyticsWidget } from './analytics-widget.js';
|
|
7
8
|
export { AnalyticsReport } from './analytics-report.js';
|
|
8
9
|
// Register custom elements so they work when imported (e.g. in designer/dashboard)
|
|
@@ -10,5 +11,6 @@ import './bar-chart.js';
|
|
|
10
11
|
import './line-chart.js';
|
|
11
12
|
import './pie-chart.js';
|
|
12
13
|
import './table.js';
|
|
14
|
+
import './number-kpi.js';
|
|
13
15
|
import './analytics-widget.js';
|
|
14
16
|
import './analytics-report.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import type { WidgetDefinitionSubset } from '../core/fetch-data.js';
|
|
3
|
+
import type { DashboardContainer } from '../dashboard/dashboard-container.js';
|
|
4
|
+
/**
|
|
5
|
+
* Number KPI widget. Displays one or more large formatted numbers with labels.
|
|
6
|
+
* Data shape from backend transformNumber(): { chartType: 'Number', values, labels, options }.
|
|
7
|
+
*/
|
|
8
|
+
export declare class NumberKpiWidget extends LitElement {
|
|
9
|
+
static styles: import("lit").CSSResult;
|
|
10
|
+
title: string;
|
|
11
|
+
hideRefreshButton: boolean;
|
|
12
|
+
dataUrl: string;
|
|
13
|
+
widgetId: string | number;
|
|
14
|
+
apiUrl: string;
|
|
15
|
+
dataParams: Record<string, string | number | boolean>;
|
|
16
|
+
dashboard: DashboardContainer | null;
|
|
17
|
+
lazy: boolean;
|
|
18
|
+
prefetchMargin: string;
|
|
19
|
+
initialDefinition: WidgetDefinitionSubset | undefined;
|
|
20
|
+
private _kpiData;
|
|
21
|
+
private _loading;
|
|
22
|
+
private _error;
|
|
23
|
+
private _dashboardParams;
|
|
24
|
+
private _viewportVisible;
|
|
25
|
+
private _definitionTitle;
|
|
26
|
+
private _loadGeneration;
|
|
27
|
+
private _hasLoadedOnce;
|
|
28
|
+
private _dashboardEl;
|
|
29
|
+
private _viewportUnobserve;
|
|
30
|
+
private _loadDataInFlight;
|
|
31
|
+
private _loadDataPendingRefresh;
|
|
32
|
+
private _loadDataScheduled;
|
|
33
|
+
constructor();
|
|
34
|
+
private _boundOnDashboardFilterChange;
|
|
35
|
+
private _boundOnDashboardRefresh;
|
|
36
|
+
connectedCallback(): void;
|
|
37
|
+
disconnectedCallback(): void;
|
|
38
|
+
updated(changed: Map<string, unknown>): void;
|
|
39
|
+
firstUpdated(): void;
|
|
40
|
+
private _attachDashboard;
|
|
41
|
+
private _detachDashboard;
|
|
42
|
+
private _getEffectiveDataUrl;
|
|
43
|
+
private _getEffectiveParams;
|
|
44
|
+
private get _effectiveTitle();
|
|
45
|
+
private _defer;
|
|
46
|
+
private _loadData;
|
|
47
|
+
private _onRefresh;
|
|
48
|
+
/** Format a number: apply decimal places, shorten_numbers (1234 → 1.2K). */
|
|
49
|
+
private _formatValue;
|
|
50
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=number-kpi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-kpi.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/number-kpi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAGrD,OAAO,KAAK,EAA0B,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAM5F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAO9E;;;GAGG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,OAAgB,MAAM,0BAsMpB;IAEkC,KAAK,EAAE,MAAM,CAAC;IACqB,iBAAiB,EAAE,OAAO,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACtD,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC,IAAI,EAAE,OAAO,CAAC;IACe,cAAc,EAAE,MAAM,CAAC;IACrD,iBAAiB,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAE1F,QAAyB,QAAQ,CAA6B;IAC9D,QAAyB,QAAQ,CAAU;IAC3C,QAAyB,MAAM,CAAgB;IAC/C,QAAyB,gBAAgB,CAA4C;IACrF,QAAyB,gBAAgB,CAAU;IACnD,QAAyB,gBAAgB,CAAS;IAElD,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,kBAAkB,CAAS;;IAsBnC,OAAO,CAAC,6BAA6B,CAInC;IAEF,OAAO,CAAC,wBAAwB,CAG9B;IAEO,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAO5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IA2B5C,YAAY,IAAI,IAAI;IAkB7B,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,KAAK,eAAe,GAE1B;IAED,OAAO,CAAC,MAAM;YAQA,SAAS;IA+DvB,OAAO,CAAC,UAAU;IAKlB,4EAA4E;IAC5E,OAAO,CAAC,YAAY;IAqBX,MAAM;CAqFhB"}
|
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { LitElement, html, css, nothing } from 'lit';
|
|
3
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import { buildWidgetDataUrl, fetchWidgetData, fetchWidgetDefinition, } from '../core/fetch-data.js';
|
|
5
|
+
import { EVENT_DASHBOARD_FILTER_CHANGE, EVENT_DASHBOARD_REFRESH, } from '../dashboard/index.js';
|
|
6
|
+
import { observeViewport } from '../core/viewport-observer.js';
|
|
7
|
+
/**
|
|
8
|
+
* Number KPI widget. Displays one or more large formatted numbers with labels.
|
|
9
|
+
* Data shape from backend transformNumber(): { chartType: 'Number', values, labels, options }.
|
|
10
|
+
*/
|
|
11
|
+
let NumberKpiWidget = class NumberKpiWidget extends LitElement {
|
|
12
|
+
static { this.styles = css `
|
|
13
|
+
:host {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.widget-wrap {
|
|
21
|
+
position: relative;
|
|
22
|
+
width: 100%;
|
|
23
|
+
height: 100%;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
min-height: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.widget-header {
|
|
30
|
+
flex-shrink: 0;
|
|
31
|
+
padding: 0.5rem 0.75rem 0.25rem 0.75rem;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
gap: 0.5rem;
|
|
37
|
+
background: var(--analytics-widget-header-bg, #fff);
|
|
38
|
+
color: var(--analytics-widget-header-color, #374151);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.widget-title {
|
|
42
|
+
margin: 0;
|
|
43
|
+
font-size: 0.9375rem;
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
color: var(--analytics-widget-header-color, #374151);
|
|
46
|
+
line-height: 1.3;
|
|
47
|
+
display: -webkit-box;
|
|
48
|
+
-webkit-box-orient: vertical;
|
|
49
|
+
-webkit-line-clamp: 1;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
text-overflow: ellipsis;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.skeleton-title {
|
|
55
|
+
height: 1rem;
|
|
56
|
+
width: 8rem;
|
|
57
|
+
max-width: 60%;
|
|
58
|
+
border-radius: 4px;
|
|
59
|
+
background: linear-gradient(90deg, #e5e7eb 0%, #e5e7eb 40%, #f3f4f6 50%, #e5e7eb 60%, #e5e7eb 100%);
|
|
60
|
+
background-size: 200% 100%;
|
|
61
|
+
animation: kpi-shimmer 1.2s ease-in-out infinite;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.btn-refresh {
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
display: inline-flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: center;
|
|
69
|
+
width: 1.75rem;
|
|
70
|
+
height: 1.75rem;
|
|
71
|
+
padding: 0;
|
|
72
|
+
border: none;
|
|
73
|
+
border-radius: 4px;
|
|
74
|
+
background: transparent;
|
|
75
|
+
color: var(--analytics-widget-header-color, #6b7280);
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
transition: color 0.15s ease, background-color 0.15s ease;
|
|
78
|
+
}
|
|
79
|
+
.btn-refresh:hover {
|
|
80
|
+
color: var(--analytics-widget-header-color, #374151);
|
|
81
|
+
background: #f3f4f6;
|
|
82
|
+
}
|
|
83
|
+
.btn-refresh:disabled {
|
|
84
|
+
cursor: not-allowed;
|
|
85
|
+
opacity: 0.6;
|
|
86
|
+
}
|
|
87
|
+
.btn-refresh svg {
|
|
88
|
+
width: 1rem;
|
|
89
|
+
height: 1rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.kpi-body {
|
|
93
|
+
flex: 1;
|
|
94
|
+
min-height: 0;
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
justify-content: center;
|
|
98
|
+
padding: var(--analytics-kpi-body-padding, 0.75rem);
|
|
99
|
+
gap: var(--analytics-kpi-gap, 1.5rem);
|
|
100
|
+
flex-wrap: wrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.kpi-block {
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
align-items: var(--analytics-kpi-align, center);
|
|
107
|
+
gap: var(--analytics-kpi-block-gap, 0.125rem);
|
|
108
|
+
min-width: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.kpi-value {
|
|
112
|
+
font-size: var(--analytics-kpi-value-size, 1.75rem);
|
|
113
|
+
font-weight: var(--analytics-kpi-value-weight, 600);
|
|
114
|
+
color: var(--analytics-kpi-value-color, #1f2937);
|
|
115
|
+
line-height: 1.2;
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
text-overflow: ellipsis;
|
|
119
|
+
max-width: 100%;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.kpi-label {
|
|
123
|
+
font-size: var(--analytics-kpi-label-size, 0.75rem);
|
|
124
|
+
font-weight: var(--analytics-kpi-label-weight, 400);
|
|
125
|
+
color: var(--analytics-kpi-label-color, #6b7280);
|
|
126
|
+
line-height: 1.3;
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
text-overflow: ellipsis;
|
|
130
|
+
max-width: 100%;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.kpi-prefix,
|
|
134
|
+
.kpi-suffix {
|
|
135
|
+
font-size: var(--analytics-kpi-affix-size, 1rem);
|
|
136
|
+
font-weight: var(--analytics-kpi-affix-weight, 400);
|
|
137
|
+
color: var(--analytics-kpi-affix-color, #6b7280);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Loading skeleton for KPI */
|
|
141
|
+
.skeleton-body {
|
|
142
|
+
flex: 1;
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
justify-content: center;
|
|
146
|
+
padding: 1rem;
|
|
147
|
+
gap: 1.5rem;
|
|
148
|
+
}
|
|
149
|
+
.skeleton-block {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: 0.375rem;
|
|
154
|
+
}
|
|
155
|
+
.skeleton-value {
|
|
156
|
+
width: 4rem;
|
|
157
|
+
height: 1.75rem;
|
|
158
|
+
border-radius: 4px;
|
|
159
|
+
background: linear-gradient(90deg, #e5e7eb 0%, #e5e7eb 40%, #f3f4f6 50%, #e5e7eb 60%, #e5e7eb 100%);
|
|
160
|
+
background-size: 200% 100%;
|
|
161
|
+
animation: kpi-shimmer 1.2s ease-in-out infinite;
|
|
162
|
+
}
|
|
163
|
+
.skeleton-label {
|
|
164
|
+
width: 5rem;
|
|
165
|
+
height: 0.75rem;
|
|
166
|
+
border-radius: 4px;
|
|
167
|
+
background: linear-gradient(90deg, #e5e7eb 0%, #e5e7eb 40%, #f3f4f6 50%, #e5e7eb 60%, #e5e7eb 100%);
|
|
168
|
+
background-size: 200% 100%;
|
|
169
|
+
animation: kpi-shimmer 1.2s ease-in-out infinite;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@keyframes kpi-shimmer {
|
|
173
|
+
to { background-position: 200% 0; }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.error {
|
|
177
|
+
color: #c00;
|
|
178
|
+
padding: 0.5rem;
|
|
179
|
+
font-size: 0.875rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.empty {
|
|
183
|
+
padding: 0.5rem;
|
|
184
|
+
color: #6b7280;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.loading-overlay {
|
|
188
|
+
position: absolute;
|
|
189
|
+
inset: 0;
|
|
190
|
+
z-index: 10;
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
justify-content: center;
|
|
194
|
+
background: rgba(255, 255, 255, 0.8);
|
|
195
|
+
backdrop-filter: blur(4px);
|
|
196
|
+
-webkit-backdrop-filter: blur(4px);
|
|
197
|
+
border-radius: 4px;
|
|
198
|
+
}
|
|
199
|
+
.loading-spinner {
|
|
200
|
+
width: 1.5rem;
|
|
201
|
+
height: 1.5rem;
|
|
202
|
+
border: 2px solid #e5e7eb;
|
|
203
|
+
border-top-color: #3b82f6;
|
|
204
|
+
border-radius: 50%;
|
|
205
|
+
animation: kpi-spin 0.7s linear infinite;
|
|
206
|
+
}
|
|
207
|
+
@keyframes kpi-spin {
|
|
208
|
+
to { transform: rotate(360deg); }
|
|
209
|
+
}
|
|
210
|
+
`; }
|
|
211
|
+
constructor() {
|
|
212
|
+
super();
|
|
213
|
+
this._loadGeneration = 0;
|
|
214
|
+
this._hasLoadedOnce = false;
|
|
215
|
+
this._dashboardEl = null;
|
|
216
|
+
this._viewportUnobserve = null;
|
|
217
|
+
this._loadDataInFlight = false;
|
|
218
|
+
this._loadDataPendingRefresh = false;
|
|
219
|
+
this._loadDataScheduled = false;
|
|
220
|
+
this._boundOnDashboardFilterChange = (e) => {
|
|
221
|
+
const ce = e;
|
|
222
|
+
this._dashboardParams = { ...(ce.detail?.params ?? {}) };
|
|
223
|
+
this._loadData();
|
|
224
|
+
};
|
|
225
|
+
this._boundOnDashboardRefresh = () => {
|
|
226
|
+
if (this.lazy && !this._viewportVisible)
|
|
227
|
+
return;
|
|
228
|
+
this._loadData();
|
|
229
|
+
};
|
|
230
|
+
this.title = '';
|
|
231
|
+
this.hideRefreshButton = false;
|
|
232
|
+
this.dataUrl = '';
|
|
233
|
+
this.widgetId = '';
|
|
234
|
+
this.apiUrl = '';
|
|
235
|
+
this.dataParams = {};
|
|
236
|
+
this.dashboard = null;
|
|
237
|
+
this.lazy = false;
|
|
238
|
+
this.prefetchMargin = '200px';
|
|
239
|
+
this.initialDefinition = undefined;
|
|
240
|
+
this._kpiData = null;
|
|
241
|
+
this._loading = false;
|
|
242
|
+
this._error = null;
|
|
243
|
+
this._dashboardParams = {};
|
|
244
|
+
this._viewportVisible = true;
|
|
245
|
+
this._definitionTitle = '';
|
|
246
|
+
}
|
|
247
|
+
connectedCallback() {
|
|
248
|
+
super.connectedCallback();
|
|
249
|
+
this._attachDashboard();
|
|
250
|
+
}
|
|
251
|
+
disconnectedCallback() {
|
|
252
|
+
this._viewportUnobserve?.();
|
|
253
|
+
this._viewportUnobserve = null;
|
|
254
|
+
this._detachDashboard();
|
|
255
|
+
super.disconnectedCallback();
|
|
256
|
+
}
|
|
257
|
+
updated(changed) {
|
|
258
|
+
if (changed.has('dashboard')) {
|
|
259
|
+
this._detachDashboard();
|
|
260
|
+
this._attachDashboard();
|
|
261
|
+
}
|
|
262
|
+
if (changed.has('widgetId') || changed.has('apiUrl')) {
|
|
263
|
+
this._definitionTitle = '';
|
|
264
|
+
}
|
|
265
|
+
if (changed.has('dataUrl') ||
|
|
266
|
+
changed.has('dataParams') ||
|
|
267
|
+
changed.has('widgetId') ||
|
|
268
|
+
changed.has('apiUrl')) {
|
|
269
|
+
if (this._getEffectiveDataUrl() && !this._loadDataScheduled) {
|
|
270
|
+
this._loadDataScheduled = true;
|
|
271
|
+
queueMicrotask(() => {
|
|
272
|
+
this._loadDataScheduled = false;
|
|
273
|
+
this._loadData();
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (changed.has('lazy')) {
|
|
278
|
+
this._viewportVisible = !this.lazy;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
firstUpdated() {
|
|
282
|
+
if (this.lazy) {
|
|
283
|
+
this._viewportVisible = false;
|
|
284
|
+
this._viewportUnobserve = observeViewport(this, {
|
|
285
|
+
rootMargin: this.prefetchMargin || '200px',
|
|
286
|
+
threshold: 0,
|
|
287
|
+
}, {
|
|
288
|
+
onEnter: () => {
|
|
289
|
+
this._viewportVisible = true;
|
|
290
|
+
if (!this._hasLoadedOnce)
|
|
291
|
+
this._loadData();
|
|
292
|
+
},
|
|
293
|
+
onLeave: () => {
|
|
294
|
+
this._viewportVisible = false;
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
_attachDashboard() {
|
|
300
|
+
const d = this.dashboard;
|
|
301
|
+
if (!d)
|
|
302
|
+
return;
|
|
303
|
+
this._dashboardEl = d;
|
|
304
|
+
this._dashboardParams = typeof d.getDashboardParams === 'function' ? d.getDashboardParams() : {};
|
|
305
|
+
d.addEventListener(EVENT_DASHBOARD_FILTER_CHANGE, this._boundOnDashboardFilterChange);
|
|
306
|
+
d.addEventListener(EVENT_DASHBOARD_REFRESH, this._boundOnDashboardRefresh);
|
|
307
|
+
}
|
|
308
|
+
_detachDashboard() {
|
|
309
|
+
const d = this._dashboardEl;
|
|
310
|
+
if (!d)
|
|
311
|
+
return;
|
|
312
|
+
d.removeEventListener(EVENT_DASHBOARD_FILTER_CHANGE, this._boundOnDashboardFilterChange);
|
|
313
|
+
d.removeEventListener(EVENT_DASHBOARD_REFRESH, this._boundOnDashboardRefresh);
|
|
314
|
+
this._dashboardEl = null;
|
|
315
|
+
this._dashboardParams = {};
|
|
316
|
+
}
|
|
317
|
+
_getEffectiveDataUrl() {
|
|
318
|
+
if (this.apiUrl && this.widgetId) {
|
|
319
|
+
return buildWidgetDataUrl(this.apiUrl, this.widgetId);
|
|
320
|
+
}
|
|
321
|
+
return this.dataUrl;
|
|
322
|
+
}
|
|
323
|
+
_getEffectiveParams() {
|
|
324
|
+
const out = { ...(this.dataParams ?? {}) };
|
|
325
|
+
if (Object.keys(this._dashboardParams).length > 0) {
|
|
326
|
+
out.dashboard_params = this._dashboardParams;
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
get _effectiveTitle() {
|
|
331
|
+
return (this.title != null && this.title !== '') ? this.title : (this._definitionTitle ?? '');
|
|
332
|
+
}
|
|
333
|
+
_defer(fn) {
|
|
334
|
+
if (typeof requestAnimationFrame !== 'undefined') {
|
|
335
|
+
requestAnimationFrame(fn);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
setTimeout(fn, 0);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async _loadData() {
|
|
342
|
+
if (this.lazy && !this._viewportVisible)
|
|
343
|
+
return;
|
|
344
|
+
const dataUrl = this._getEffectiveDataUrl();
|
|
345
|
+
if (!dataUrl) {
|
|
346
|
+
this._error = null;
|
|
347
|
+
this._loading = false;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (this._loadDataInFlight) {
|
|
351
|
+
this._loadDataPendingRefresh = true;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const gen = ++this._loadGeneration;
|
|
355
|
+
this._loadDataInFlight = true;
|
|
356
|
+
this._loading = true;
|
|
357
|
+
this._error = null;
|
|
358
|
+
try {
|
|
359
|
+
const useWidgetApi = Boolean(this.apiUrl && this.widgetId);
|
|
360
|
+
let params;
|
|
361
|
+
if (useWidgetApi && !this._definitionTitle) {
|
|
362
|
+
const def = this.initialDefinition ?? (await fetchWidgetDefinition(this.apiUrl, this.widgetId));
|
|
363
|
+
this._definitionTitle = def.title ?? '';
|
|
364
|
+
params = { ...(this.dataParams ?? {}) };
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
params = this._getEffectiveParams();
|
|
368
|
+
}
|
|
369
|
+
if (Object.keys(this._dashboardParams).length > 0) {
|
|
370
|
+
params.dashboard_params = this._dashboardParams;
|
|
371
|
+
}
|
|
372
|
+
const res = await fetchWidgetData(dataUrl, params);
|
|
373
|
+
if (gen !== this._loadGeneration)
|
|
374
|
+
return;
|
|
375
|
+
const wd = res.widgetData;
|
|
376
|
+
const kpiData = wd && wd.chartType === 'Number'
|
|
377
|
+
? { chartType: 'Number', values: wd.values ?? [], labels: wd.labels ?? [], options: wd.options }
|
|
378
|
+
: null;
|
|
379
|
+
this._defer(() => {
|
|
380
|
+
if (gen !== this._loadGeneration)
|
|
381
|
+
return;
|
|
382
|
+
this._loadDataInFlight = false;
|
|
383
|
+
this._kpiData = kpiData;
|
|
384
|
+
this._hasLoadedOnce = true;
|
|
385
|
+
this._loading = false;
|
|
386
|
+
if (this._loadDataPendingRefresh) {
|
|
387
|
+
this._loadDataPendingRefresh = false;
|
|
388
|
+
this._loadData();
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
catch (e) {
|
|
393
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
394
|
+
this._defer(() => {
|
|
395
|
+
if (gen !== this._loadGeneration)
|
|
396
|
+
return;
|
|
397
|
+
this._loadDataInFlight = false;
|
|
398
|
+
this._error = errMsg;
|
|
399
|
+
this._kpiData = null;
|
|
400
|
+
this._loading = false;
|
|
401
|
+
if (this._loadDataPendingRefresh) {
|
|
402
|
+
this._loadDataPendingRefresh = false;
|
|
403
|
+
this._loadData();
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
_onRefresh() {
|
|
409
|
+
if (this._loading)
|
|
410
|
+
return;
|
|
411
|
+
this._loadData();
|
|
412
|
+
}
|
|
413
|
+
/** Format a number: apply decimal places, shorten_numbers (1234 → 1.2K). */
|
|
414
|
+
_formatValue(value, decimal) {
|
|
415
|
+
const dec = decimal != null && decimal !== '' ? parseInt(decimal, 10) : undefined;
|
|
416
|
+
const abs = Math.abs(value);
|
|
417
|
+
// shorten_numbers: from widget config (checked via options or future config)
|
|
418
|
+
if (abs >= 1_000_000_000) {
|
|
419
|
+
const v = value / 1_000_000_000;
|
|
420
|
+
return (dec != null ? v.toFixed(dec) : v.toFixed(1)) + 'B';
|
|
421
|
+
}
|
|
422
|
+
if (abs >= 1_000_000) {
|
|
423
|
+
const v = value / 1_000_000;
|
|
424
|
+
return (dec != null ? v.toFixed(dec) : v.toFixed(1)) + 'M';
|
|
425
|
+
}
|
|
426
|
+
if (abs >= 10_000) {
|
|
427
|
+
const v = value / 1_000;
|
|
428
|
+
return (dec != null ? v.toFixed(dec) : v.toFixed(1)) + 'K';
|
|
429
|
+
}
|
|
430
|
+
if (dec != null)
|
|
431
|
+
return value.toFixed(dec);
|
|
432
|
+
if (Number.isInteger(value))
|
|
433
|
+
return value.toLocaleString();
|
|
434
|
+
return value.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
435
|
+
}
|
|
436
|
+
render() {
|
|
437
|
+
const canRefresh = Boolean(this._getEffectiveDataUrl()) && !this.hideRefreshButton;
|
|
438
|
+
const showSkeleton = this._loading && !this._hasLoadedOnce;
|
|
439
|
+
const showSpinnerOverlay = this._loading && this._hasLoadedOnce;
|
|
440
|
+
const showTitlePlaceholder = !this._effectiveTitle && !this.hideRefreshButton;
|
|
441
|
+
const headerContent = this._effectiveTitle || canRefresh || showTitlePlaceholder
|
|
442
|
+
? html `
|
|
443
|
+
<header class="widget-header">
|
|
444
|
+
${this._effectiveTitle
|
|
445
|
+
? html `<h2 class="widget-title">${this._effectiveTitle}</h2>`
|
|
446
|
+
: showTitlePlaceholder
|
|
447
|
+
? html `<div class="skeleton-title" aria-hidden="true"></div>`
|
|
448
|
+
: html `<span></span>`}
|
|
449
|
+
${canRefresh
|
|
450
|
+
? html `
|
|
451
|
+
<button
|
|
452
|
+
type="button"
|
|
453
|
+
class="btn-refresh"
|
|
454
|
+
title="Refresh"
|
|
455
|
+
aria-label="Refresh data"
|
|
456
|
+
?disabled=${this._loading}
|
|
457
|
+
@click=${this._onRefresh}
|
|
458
|
+
>
|
|
459
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
460
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
|
461
|
+
<path d="M3 3v5h5" />
|
|
462
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
|
|
463
|
+
<path d="M16 21h5v-5" />
|
|
464
|
+
</svg>
|
|
465
|
+
</button>
|
|
466
|
+
`
|
|
467
|
+
: ''}
|
|
468
|
+
</header>
|
|
469
|
+
`
|
|
470
|
+
: '';
|
|
471
|
+
const bodyContent = showSkeleton
|
|
472
|
+
? html `
|
|
473
|
+
<div class="skeleton-body" aria-busy="true" aria-live="polite">
|
|
474
|
+
<div class="skeleton-block">
|
|
475
|
+
<div class="skeleton-value"></div>
|
|
476
|
+
<div class="skeleton-label"></div>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
`
|
|
480
|
+
: this._error
|
|
481
|
+
? html `<div class="error">${this._error}</div>`
|
|
482
|
+
: !this._kpiData || this._kpiData.values.length === 0
|
|
483
|
+
? html `<div class="empty">No data</div>`
|
|
484
|
+
: html `
|
|
485
|
+
<div class="kpi-body">
|
|
486
|
+
${this._kpiData.values.map((value, i) => {
|
|
487
|
+
const label = this._kpiData.labels[i] ?? '';
|
|
488
|
+
const opt = this._kpiData.options?.[i];
|
|
489
|
+
const prefix = opt?.prefix ?? '';
|
|
490
|
+
const suffix = opt?.suffix ?? '';
|
|
491
|
+
const formatted = this._formatValue(value, opt?.decimal);
|
|
492
|
+
return html `
|
|
493
|
+
<div class="kpi-block">
|
|
494
|
+
<span class="kpi-value">
|
|
495
|
+
${prefix ? html `<span class="kpi-prefix">${prefix}</span>` : nothing}
|
|
496
|
+
${formatted}
|
|
497
|
+
${suffix ? html `<span class="kpi-suffix">${suffix}</span>` : nothing}
|
|
498
|
+
</span>
|
|
499
|
+
${label ? html `<span class="kpi-label">${label}</span>` : nothing}
|
|
500
|
+
</div>
|
|
501
|
+
`;
|
|
502
|
+
})}
|
|
503
|
+
</div>
|
|
504
|
+
`;
|
|
505
|
+
return html `
|
|
506
|
+
<div class="widget-wrap">
|
|
507
|
+
${headerContent}
|
|
508
|
+
${bodyContent}
|
|
509
|
+
${showSpinnerOverlay
|
|
510
|
+
? html `<div class="loading-overlay" aria-busy="true" aria-live="polite">
|
|
511
|
+
<div class="loading-spinner" aria-hidden="true"></div>
|
|
512
|
+
</div>`
|
|
513
|
+
: nothing}
|
|
514
|
+
</div>
|
|
515
|
+
`;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
__decorate([
|
|
519
|
+
property({ type: String })
|
|
520
|
+
], NumberKpiWidget.prototype, "title", void 0);
|
|
521
|
+
__decorate([
|
|
522
|
+
property({ type: Boolean, attribute: 'hide-refresh-button' })
|
|
523
|
+
], NumberKpiWidget.prototype, "hideRefreshButton", void 0);
|
|
524
|
+
__decorate([
|
|
525
|
+
property({ type: String, attribute: 'data-url' })
|
|
526
|
+
], NumberKpiWidget.prototype, "dataUrl", void 0);
|
|
527
|
+
__decorate([
|
|
528
|
+
property({ type: String, attribute: 'widget-id' })
|
|
529
|
+
], NumberKpiWidget.prototype, "widgetId", void 0);
|
|
530
|
+
__decorate([
|
|
531
|
+
property({ type: String, attribute: 'api-url' })
|
|
532
|
+
], NumberKpiWidget.prototype, "apiUrl", void 0);
|
|
533
|
+
__decorate([
|
|
534
|
+
property({ type: Object })
|
|
535
|
+
], NumberKpiWidget.prototype, "dataParams", void 0);
|
|
536
|
+
__decorate([
|
|
537
|
+
property({ type: Object })
|
|
538
|
+
], NumberKpiWidget.prototype, "dashboard", void 0);
|
|
539
|
+
__decorate([
|
|
540
|
+
property({ type: Boolean })
|
|
541
|
+
], NumberKpiWidget.prototype, "lazy", void 0);
|
|
542
|
+
__decorate([
|
|
543
|
+
property({ type: String, attribute: 'prefetch-margin' })
|
|
544
|
+
], NumberKpiWidget.prototype, "prefetchMargin", void 0);
|
|
545
|
+
__decorate([
|
|
546
|
+
property({ type: Object })
|
|
547
|
+
], NumberKpiWidget.prototype, "initialDefinition", void 0);
|
|
548
|
+
__decorate([
|
|
549
|
+
state()
|
|
550
|
+
], NumberKpiWidget.prototype, "_kpiData", void 0);
|
|
551
|
+
__decorate([
|
|
552
|
+
state()
|
|
553
|
+
], NumberKpiWidget.prototype, "_loading", void 0);
|
|
554
|
+
__decorate([
|
|
555
|
+
state()
|
|
556
|
+
], NumberKpiWidget.prototype, "_error", void 0);
|
|
557
|
+
__decorate([
|
|
558
|
+
state()
|
|
559
|
+
], NumberKpiWidget.prototype, "_dashboardParams", void 0);
|
|
560
|
+
__decorate([
|
|
561
|
+
state()
|
|
562
|
+
], NumberKpiWidget.prototype, "_viewportVisible", void 0);
|
|
563
|
+
__decorate([
|
|
564
|
+
state()
|
|
565
|
+
], NumberKpiWidget.prototype, "_definitionTitle", void 0);
|
|
566
|
+
NumberKpiWidget = __decorate([
|
|
567
|
+
customElement('analytics-number-kpi')
|
|
568
|
+
], NumberKpiWidget);
|
|
569
|
+
export { NumberKpiWidget };
|
package/widgets/table.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAGrD,OAAO,KAAK,EAA0B,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAM5F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAM9E,OAAO,qBAAqB,CAAC;AAE7B;;;;;GAKG;AACH,qBACa,WAAY,SAAQ,UAAU;IACzC,OAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/table.ts"],"names":[],"mappings":"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"}
|
package/widgets/table.js
CHANGED
|
@@ -75,15 +75,54 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
75
75
|
.loading-overlay {
|
|
76
76
|
position: absolute;
|
|
77
77
|
inset: 0;
|
|
78
|
-
z-index:
|
|
78
|
+
z-index: 10;
|
|
79
79
|
display: flex;
|
|
80
80
|
align-items: center;
|
|
81
81
|
justify-content: center;
|
|
82
|
-
background: rgba(255, 255, 255, 0.
|
|
82
|
+
background: rgba(255, 255, 255, 0.8);
|
|
83
83
|
backdrop-filter: blur(4px);
|
|
84
84
|
-webkit-backdrop-filter: blur(4px);
|
|
85
85
|
border-radius: 4px;
|
|
86
86
|
}
|
|
87
|
+
.skeleton {
|
|
88
|
+
width: 100%;
|
|
89
|
+
padding: 0.75rem 1rem;
|
|
90
|
+
box-sizing: border-box;
|
|
91
|
+
}
|
|
92
|
+
.skeleton-row {
|
|
93
|
+
display: flex;
|
|
94
|
+
gap: 0.75rem;
|
|
95
|
+
align-items: center;
|
|
96
|
+
padding: 0.5rem 0;
|
|
97
|
+
border-bottom: 1px solid #f3f4f6;
|
|
98
|
+
}
|
|
99
|
+
.skeleton-cell {
|
|
100
|
+
height: 1rem;
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
background: linear-gradient(
|
|
103
|
+
90deg,
|
|
104
|
+
#e5e7eb 0%,
|
|
105
|
+
#e5e7eb 40%,
|
|
106
|
+
#f3f4f6 50%,
|
|
107
|
+
#e5e7eb 60%,
|
|
108
|
+
#e5e7eb 100%
|
|
109
|
+
);
|
|
110
|
+
background-size: 200% 100%;
|
|
111
|
+
animation: analytics-table-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
112
|
+
}
|
|
113
|
+
.skeleton-row:first-child .skeleton-cell {
|
|
114
|
+
height: 1.25rem;
|
|
115
|
+
background: #f3f4f6;
|
|
116
|
+
}
|
|
117
|
+
.skeleton-row:first-child .skeleton-cell:nth-child(1) { flex: 1.2; }
|
|
118
|
+
.skeleton-row:first-child .skeleton-cell:nth-child(2) { flex: 1; }
|
|
119
|
+
.skeleton-row:first-child .skeleton-cell:nth-child(3) { flex: 0.8; }
|
|
120
|
+
.skeleton-row:not(:first-child) .skeleton-cell:nth-child(1) { flex: 1.2; min-width: 0; }
|
|
121
|
+
.skeleton-row:not(:first-child) .skeleton-cell:nth-child(2) { flex: 1; min-width: 0; }
|
|
122
|
+
.skeleton-row:not(:first-child) .skeleton-cell:nth-child(3) { flex: 0.8; min-width: 0; }
|
|
123
|
+
@keyframes analytics-table-skeleton-shimmer {
|
|
124
|
+
to { background-position: 200% 0; }
|
|
125
|
+
}
|
|
87
126
|
.loading-spinner {
|
|
88
127
|
width: 2rem;
|
|
89
128
|
height: 2rem;
|
|
@@ -167,6 +206,22 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
167
206
|
overflow: hidden;
|
|
168
207
|
text-overflow: ellipsis;
|
|
169
208
|
}
|
|
209
|
+
.skeleton-title {
|
|
210
|
+
height: 1rem;
|
|
211
|
+
width: 8rem;
|
|
212
|
+
max-width: 60%;
|
|
213
|
+
border-radius: 4px;
|
|
214
|
+
background: linear-gradient(
|
|
215
|
+
90deg,
|
|
216
|
+
#e5e7eb 0%,
|
|
217
|
+
#e5e7eb 40%,
|
|
218
|
+
#f3f4f6 50%,
|
|
219
|
+
#e5e7eb 60%,
|
|
220
|
+
#e5e7eb 100%
|
|
221
|
+
);
|
|
222
|
+
background-size: 200% 100%;
|
|
223
|
+
animation: analytics-table-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
224
|
+
}
|
|
170
225
|
.widget-body {
|
|
171
226
|
flex: 1;
|
|
172
227
|
min-height: 0;
|
|
@@ -299,10 +354,8 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
299
354
|
}, {
|
|
300
355
|
onEnter: () => {
|
|
301
356
|
this._viewportVisible = true;
|
|
302
|
-
if (!this._hasLoadedOnce)
|
|
303
|
-
this._hasLoadedOnce = true;
|
|
357
|
+
if (!this._hasLoadedOnce)
|
|
304
358
|
this._loadData();
|
|
305
|
-
}
|
|
306
359
|
},
|
|
307
360
|
onLeave: () => {
|
|
308
361
|
this._viewportVisible = false;
|
|
@@ -446,6 +499,7 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
446
499
|
this._widgetFilters = [];
|
|
447
500
|
if (hideFilter !== undefined)
|
|
448
501
|
this._hideFilter = hideFilter;
|
|
502
|
+
this._hasLoadedOnce = true;
|
|
449
503
|
this._loading = false;
|
|
450
504
|
if (this._loadDataPendingRefresh) {
|
|
451
505
|
this._loadDataPendingRefresh = false;
|
|
@@ -489,6 +543,7 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
489
543
|
this._widgetFilters = [];
|
|
490
544
|
if (hideFilter !== undefined)
|
|
491
545
|
this._hideFilter = hideFilter;
|
|
546
|
+
this._hasLoadedOnce = true;
|
|
492
547
|
this._loading = false;
|
|
493
548
|
if (this._loadDataPendingRefresh) {
|
|
494
549
|
this._loadDataPendingRefresh = false;
|
|
@@ -577,10 +632,16 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
577
632
|
const hasHeaderRows = Boolean(wd?.header_rows?.length);
|
|
578
633
|
const tableFontSizeStyle = wd?.font_size ? `font-size: ${wd.font_size}` : nothing;
|
|
579
634
|
const canRefresh = Boolean(this._getEffectiveDataUrl()) && !this.hideRefreshButton;
|
|
580
|
-
const
|
|
635
|
+
const showSkeleton = this._loading && !this._hasLoadedOnce;
|
|
636
|
+
const showTitlePlaceholder = !this._effectiveTitle && !this.hideRefreshButton;
|
|
637
|
+
const headerContent = this._effectiveTitle || canRefresh || showTitlePlaceholder
|
|
581
638
|
? html `
|
|
582
639
|
<header class="widget-header">
|
|
583
|
-
${this._effectiveTitle
|
|
640
|
+
${this._effectiveTitle
|
|
641
|
+
? html `<h2 class="widget-title">${this._effectiveTitle}</h2>`
|
|
642
|
+
: showTitlePlaceholder
|
|
643
|
+
? html `<div class="skeleton-title" aria-hidden="true"></div>`
|
|
644
|
+
: html `<span></span>`}
|
|
584
645
|
${canRefresh
|
|
585
646
|
? html `
|
|
586
647
|
<button
|
|
@@ -613,12 +674,43 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
613
674
|
></analytics-widget-toolbar>
|
|
614
675
|
`
|
|
615
676
|
: html ``;
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
677
|
+
const showSpinnerOverlay = this._loading && this._hasLoadedOnce;
|
|
678
|
+
const bodyContent = showSkeleton
|
|
679
|
+
? html `
|
|
680
|
+
<div class="skeleton" aria-busy="true" aria-live="polite">
|
|
681
|
+
<div class="skeleton-row">
|
|
682
|
+
<div class="skeleton-cell"></div>
|
|
683
|
+
<div class="skeleton-cell"></div>
|
|
684
|
+
<div class="skeleton-cell"></div>
|
|
685
|
+
</div>
|
|
686
|
+
<div class="skeleton-row">
|
|
687
|
+
<div class="skeleton-cell"></div>
|
|
688
|
+
<div class="skeleton-cell"></div>
|
|
689
|
+
<div class="skeleton-cell"></div>
|
|
690
|
+
</div>
|
|
691
|
+
<div class="skeleton-row">
|
|
692
|
+
<div class="skeleton-cell"></div>
|
|
693
|
+
<div class="skeleton-cell"></div>
|
|
694
|
+
<div class="skeleton-cell"></div>
|
|
695
|
+
</div>
|
|
696
|
+
<div class="skeleton-row">
|
|
697
|
+
<div class="skeleton-cell"></div>
|
|
698
|
+
<div class="skeleton-cell"></div>
|
|
699
|
+
<div class="skeleton-cell"></div>
|
|
700
|
+
</div>
|
|
701
|
+
<div class="skeleton-row">
|
|
702
|
+
<div class="skeleton-cell"></div>
|
|
703
|
+
<div class="skeleton-cell"></div>
|
|
704
|
+
<div class="skeleton-cell"></div>
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
`
|
|
708
|
+
: this._error
|
|
709
|
+
? html `<div class="error">${this._error}</div>`
|
|
710
|
+
: columns.length === 0
|
|
711
|
+
? html `<div class="empty">No columns</div>`
|
|
712
|
+
: rows.length === 0
|
|
713
|
+
? html `
|
|
622
714
|
<table style=${tableFontSizeStyle}>
|
|
623
715
|
<thead>
|
|
624
716
|
${this._renderThead(columns, hasHeaderRows)}
|
|
@@ -630,7 +722,7 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
630
722
|
</tbody>
|
|
631
723
|
</table>
|
|
632
724
|
`
|
|
633
|
-
|
|
725
|
+
: html `
|
|
634
726
|
<table style=${tableFontSizeStyle}>
|
|
635
727
|
<thead>
|
|
636
728
|
${this._renderThead(columns, hasHeaderRows)}
|
|
@@ -645,12 +737,12 @@ let TableWidget = class TableWidget extends LitElement {
|
|
|
645
737
|
${headerContent}
|
|
646
738
|
<div class="widget-body">
|
|
647
739
|
${bodyContent}
|
|
648
|
-
|
|
740
|
+
</div>
|
|
741
|
+
${showSpinnerOverlay
|
|
649
742
|
? html `<div class="loading-overlay" aria-busy="true" aria-live="polite">
|
|
650
|
-
|
|
651
|
-
|
|
743
|
+
<div class="loading-spinner" aria-hidden="true"></div>
|
|
744
|
+
</div>`
|
|
652
745
|
: nothing}
|
|
653
|
-
</div>
|
|
654
746
|
${filterToolbar}
|
|
655
747
|
</div>
|
|
656
748
|
`;
|