@quicdata/analytics 0.0.7 → 0.0.9

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.
@@ -122,7 +122,7 @@ let AnalyticsWidgetToolbar = class AnalyticsWidgetToolbar extends LitElement {
122
122
  static { this.styles = css `
123
123
  :host {
124
124
  position: absolute;
125
- top: 0.5rem;
125
+ top: var(--analytics-widget-toolbar-top, 0.5rem);
126
126
  right: 0.5rem;
127
127
  z-index: var(--analytics-widget-toolbar-z-index, 20);
128
128
  opacity: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quicdata/analytics",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -26,6 +26,7 @@ export declare class AnalyticsReport extends LitElement {
26
26
  private _excelUrlBase;
27
27
  private _loading;
28
28
  private _error;
29
+ private _refreshKey;
29
30
  constructor();
30
31
  connectedCallback(): void;
31
32
  disconnectedCallback(): void;
@@ -39,6 +40,7 @@ export declare class AnalyticsReport extends LitElement {
39
40
  private get _pdfUrl();
40
41
  private get _excelUrl();
41
42
  private _print;
43
+ private _onRefresh;
42
44
  render(): import("lit-html").TemplateResult<1> | typeof nothing;
43
45
  }
44
46
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"analytics-report.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/analytics-report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAYrD,OAAO,0BAA0B,CAAC;AAClC,OAAO,YAAY,CAAC;AAEpB;;;;GAIG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,OAAgB,MAAM,0BAyFpB;IAEF,oHAAoH;IACxD,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtF,0CAA0C;IACgB,MAAM,EAAE,MAAM,CAAC;IAEzE,gJAAgJ;IACjF,WAAW,EAAE,MAAM,CAAC;IAEnF,6DAA6D;IACzB,KAAK,EAAE,MAAM,CAAC;IAElD,sEAAsE;IAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAE1F,QAAyB,KAAK,CAAS;IACvC,QAAyB,QAAQ,CAAqB;IACtD,QAAyB,OAAO,CAA4C;IAC5E,QAAyB,eAAe,CAAS;IACjD,QAAyB,WAAW,CAAS;IAC7C,QAAyB,aAAa,CAAS;IAC/C,QAAyB,QAAQ,CAAU;IAC3C,QAAyB,MAAM,CAAgB;;IAmBtC,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAK5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;YAevC,WAAW;IAwBzB,wJAAwJ;IACxJ,OAAO,CAAC,+BAA+B;IAWvC,OAAO,CAAC,eAAe;IAMvB,OAAO,KAAK,QAAQ,GAGnB;IAED,OAAO,KAAK,WAAW,GAItB;IAED,OAAO,KAAK,OAAO,GAIlB;IAED,OAAO,KAAK,SAAS,GAIpB;IAED,OAAO,CAAC,MAAM;IAIL,MAAM;CAiDhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,eAAe,CAAC;KACrC;CACF"}
1
+ {"version":3,"file":"analytics-report.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/analytics-report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAYrD,OAAO,0BAA0B,CAAC;AAClC,OAAO,YAAY,CAAC;AAEpB;;;;GAIG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,OAAgB,MAAM,0BAiHpB;IAEF,oHAAoH;IACxD,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtF,0CAA0C;IACgB,MAAM,EAAE,MAAM,CAAC;IAEzE,gJAAgJ;IACjF,WAAW,EAAE,MAAM,CAAC;IAEnF,6DAA6D;IACzB,KAAK,EAAE,MAAM,CAAC;IAElD,sEAAsE;IAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAE1F,QAAyB,KAAK,CAAS;IACvC,QAAyB,QAAQ,CAAqB;IACtD,QAAyB,OAAO,CAA4C;IAC5E,QAAyB,eAAe,CAAS;IACjD,QAAyB,WAAW,CAAS;IAC7C,QAAyB,aAAa,CAAS;IAC/C,QAAyB,QAAQ,CAAU;IAC3C,QAAyB,MAAM,CAAgB;IACtC,OAAO,CAAC,WAAW,CAAK;;IAmBxB,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAK5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;YAevC,WAAW;IAwBzB,wJAAwJ;IACxJ,OAAO,CAAC,+BAA+B;IAWvC,OAAO,CAAC,eAAe;IAMvB,OAAO,KAAK,QAAQ,GAGnB;IAED,OAAO,KAAK,WAAW,GAItB;IAED,OAAO,KAAK,OAAO,GAIlB;IAED,OAAO,KAAK,SAAS,GAIpB;IAED,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,UAAU;IAIT,MAAM;CAgEhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,eAAe,CAAC;KACrC;CACF"}
@@ -34,11 +34,12 @@ let AnalyticsReport = class AnalyticsReport extends LitElement {
34
34
  padding: 0.5rem 1rem;
35
35
  border-bottom: 1px solid var(--analytics-report-border, #e2e8f0);
36
36
  background: var(--analytics-report-header-bg, #f8fafc);
37
+ color: var(--analytics-report-header-color, #1e293b);
37
38
  }
38
39
  .report-title {
39
40
  font-size: 1rem;
40
41
  font-weight: 600;
41
- color: var(--analytics-report-title-color, #1e293b);
42
+ color: var(--analytics-report-header-color, #1e293b);
42
43
  margin: 0;
43
44
  }
44
45
  .report-actions {
@@ -62,6 +63,29 @@ let AnalyticsReport = class AnalyticsReport extends LitElement {
62
63
  background: var(--analytics-report-btn-hover-bg, #f1f5f9);
63
64
  color: var(--analytics-report-btn-hover-color, #0f172a);
64
65
  }
66
+ .btn-refresh,
67
+ .report-actions button.btn-refresh {
68
+ display: inline-flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ width: 1.75rem;
72
+ height: 1.75rem;
73
+ padding: 0;
74
+ border: none;
75
+ border-radius: 4px;
76
+ background: transparent;
77
+ color: var(--analytics-report-header-color, #334155);
78
+ cursor: pointer;
79
+ transition: color 0.15s ease, background-color 0.15s ease;
80
+ }
81
+ .btn-refresh:hover {
82
+ color: var(--analytics-report-header-color, #0f172a);
83
+ background: var(--analytics-report-btn-hover-bg, #f1f5f9);
84
+ }
85
+ .btn-refresh svg {
86
+ width: 1rem;
87
+ height: 1rem;
88
+ }
65
89
  .report-filters {
66
90
  flex-shrink: 0;
67
91
  border-bottom: 1px solid var(--analytics-report-border, #e2e8f0);
@@ -103,6 +127,7 @@ let AnalyticsReport = class AnalyticsReport extends LitElement {
103
127
  `; }
104
128
  constructor() {
105
129
  super();
130
+ this._refreshKey = 0;
106
131
  this.reportId = '';
107
132
  this.apiUrl = '';
108
133
  this.customTitle = '';
@@ -213,6 +238,9 @@ let AnalyticsReport = class AnalyticsReport extends LitElement {
213
238
  _print() {
214
239
  window.open(this._previewUrl, '_blank', 'noopener')?.print();
215
240
  }
241
+ _onRefresh() {
242
+ this._refreshKey = Date.now();
243
+ }
216
244
  render() {
217
245
  if (this._loading) {
218
246
  return html `
@@ -234,6 +262,20 @@ let AnalyticsReport = class AnalyticsReport extends LitElement {
234
262
  <div class="report-header">
235
263
  <h1 class="report-title">${displayTitle}</h1>
236
264
  <div class="report-actions">
265
+ <button
266
+ type="button"
267
+ class="btn-refresh"
268
+ title="Refresh"
269
+ aria-label="Refresh data"
270
+ @click=${this._onRefresh}
271
+ >
272
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
273
+ <path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
274
+ <path d="M3 3v5h5" />
275
+ <path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
276
+ <path d="M16 21h5v-5" />
277
+ </svg>
278
+ </button>
237
279
  <button type="button" @click=${this._print}>Print</button>
238
280
  <a href=${this._pdfUrl} target="_blank" rel="noopener" download>Download PDF</a>
239
281
  <a href=${this._excelUrl} target="_blank" rel="noopener" download>Download Excel</a>
@@ -253,8 +295,9 @@ let AnalyticsReport = class AnalyticsReport extends LitElement {
253
295
  <div class="report-table-wrap">
254
296
  <analytics-table
255
297
  data-url=${this._dataUrl}
256
- .dataParams=${this._params}
298
+ .dataParams=${{ ...this._params, _refresh: this._refreshKey }}
257
299
  .title=${''}
300
+ hide-refresh-button
258
301
  ></analytics-table>
259
302
  </div>
260
303
  </div>
@@ -300,6 +343,9 @@ __decorate([
300
343
  __decorate([
301
344
  state()
302
345
  ], AnalyticsReport.prototype, "_error", void 0);
346
+ __decorate([
347
+ state()
348
+ ], AnalyticsReport.prototype, "_refreshKey", void 0);
303
349
  AnalyticsReport = __decorate([
304
350
  customElement('analytics-report')
305
351
  ], AnalyticsReport);
@@ -88,6 +88,7 @@ export declare abstract class BaseChartWidget extends LitElement {
88
88
  type?: string;
89
89
  }>, _widgetData?: unknown): EChartsOption;
90
90
  private _onWidgetFilterChange;
91
+ private _onRefresh;
91
92
  render(): import("lit-html").TemplateResult<1>;
92
93
  }
93
94
  //# sourceMappingURL=base-chart.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-chart.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/base-chart.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base-chart.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/base-chart.ts"],"names":[],"mappings":"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"}
@@ -41,15 +41,122 @@ export class BaseChartWidget extends LitElement {
41
41
  .loading-overlay {
42
42
  position: absolute;
43
43
  inset: 0;
44
- z-index: 2;
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.65);
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;
@@ -68,6 +175,7 @@ export class BaseChartWidget extends LitElement {
68
175
  min-height: 120px;
69
176
  display: flex;
70
177
  flex-direction: column;
178
+ --analytics-widget-toolbar-top: 2.5rem;
71
179
  }
72
180
  .widget-with-toolbar:hover analytics-widget-toolbar {
73
181
  opacity: 1;
@@ -77,12 +185,18 @@ export class BaseChartWidget extends LitElement {
77
185
  flex-shrink: 0;
78
186
  padding: 0.5rem 0.75rem 0.25rem 0.75rem;
79
187
  min-width: 0;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: space-between;
191
+ gap: 0.5rem;
192
+ background: var(--analytics-widget-header-bg, #fff);
193
+ color: var(--analytics-widget-header-color, #374151);
80
194
  }
81
195
  .widget-title {
82
196
  margin: 0;
83
197
  font-size: 0.9375rem;
84
198
  font-weight: 600;
85
- color: #374151;
199
+ color: var(--analytics-widget-header-color, #374151);
86
200
  line-height: 1.3;
87
201
  display: -webkit-box;
88
202
  -webkit-box-orient: vertical;
@@ -90,6 +204,22 @@ export class BaseChartWidget extends LitElement {
90
204
  overflow: hidden;
91
205
  text-overflow: ellipsis;
92
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
+ }
93
223
  .widget-body {
94
224
  flex: 1;
95
225
  min-height: 0;
@@ -100,6 +230,33 @@ export class BaseChartWidget extends LitElement {
100
230
  height: 100%;
101
231
  min-height: 100px;
102
232
  }
233
+ .btn-refresh {
234
+ flex-shrink: 0;
235
+ display: inline-flex;
236
+ align-items: center;
237
+ justify-content: center;
238
+ width: 1.75rem;
239
+ height: 1.75rem;
240
+ padding: 0;
241
+ border: none;
242
+ border-radius: 4px;
243
+ background: transparent;
244
+ color: var(--analytics-widget-header-color, #6b7280);
245
+ cursor: pointer;
246
+ transition: color 0.15s ease, background-color 0.15s ease;
247
+ }
248
+ .btn-refresh:hover {
249
+ color: var(--analytics-widget-header-color, #374151);
250
+ background: #f3f4f6;
251
+ }
252
+ .btn-refresh:disabled {
253
+ cursor: not-allowed;
254
+ opacity: 0.6;
255
+ }
256
+ .btn-refresh svg {
257
+ width: 1rem;
258
+ height: 1rem;
259
+ }
103
260
  `; }
104
261
  /** Delay (ms) after last resize event before calling chart.resize(). */
105
262
  static { this._RESIZE_DELAY_MS = 300; }
@@ -241,10 +398,8 @@ export class BaseChartWidget extends LitElement {
241
398
  this._viewportUnobserve = observeViewport(this, { rootMargin: this.prefetchMargin || '200px', threshold: 0 }, {
242
399
  onEnter: () => {
243
400
  this._viewportVisible = true;
244
- if (!this._hasLoadedOnce) {
245
- this._hasLoadedOnce = true;
401
+ if (!this._hasLoadedOnce)
246
402
  this._loadData();
247
- }
248
403
  },
249
404
  onLeave: () => {
250
405
  this._viewportVisible = false;
@@ -355,6 +510,7 @@ export class BaseChartWidget extends LitElement {
355
510
  this._widgetFilters = filters;
356
511
  if (hideFilter !== undefined)
357
512
  this._hideFilter = hideFilter;
513
+ this._hasLoadedOnce = true;
358
514
  this._loading = false;
359
515
  this._renderChart();
360
516
  });
@@ -372,6 +528,7 @@ export class BaseChartWidget extends LitElement {
372
528
  this.data = data;
373
529
  this.meta = meta;
374
530
  this._widgetData = null;
531
+ this._hasLoadedOnce = true;
375
532
  this._loading = false;
376
533
  this._renderChart();
377
534
  });
@@ -427,24 +584,103 @@ export class BaseChartWidget extends LitElement {
427
584
  _onWidgetFilterChange(e) {
428
585
  this._widgetFilterParams = { ...(e.detail?.params ?? {}) };
429
586
  }
587
+ _onRefresh() {
588
+ if (this._loading)
589
+ return;
590
+ this._loadData();
591
+ }
430
592
  render() {
431
593
  const hasWidgetFilters = this._widgetFilters.length > 0 && !this._hideFilter;
432
594
  const widgetActiveCount = Object.keys(this._widgetFilterParams).filter((k) => this._widgetFilterParams[k] !== '' && this._widgetFilterParams[k] != null).length;
595
+ const canRefresh = Boolean(this._getEffectiveDataUrl());
596
+ const showSkeleton = this._loading && !this._hasLoadedOnce;
597
+ const showTitlePlaceholder = !this._effectiveTitle;
598
+ const headerContent = this._effectiveTitle || canRefresh || showTitlePlaceholder
599
+ ? html `
600
+ <header class="widget-header">
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>`}
606
+ ${canRefresh
607
+ ? html `
608
+ <button
609
+ type="button"
610
+ class="btn-refresh"
611
+ title="Refresh"
612
+ aria-label="Refresh data"
613
+ ?disabled=${this._loading}
614
+ @click=${this._onRefresh}
615
+ >
616
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
617
+ <path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
618
+ <path d="M3 3v5h5" />
619
+ <path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
620
+ <path d="M16 21h5v-5" />
621
+ </svg>
622
+ </button>
623
+ `
624
+ : ''}
625
+ </header>
626
+ `
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;
433
669
  return html `
434
670
  <div class="widget-with-toolbar">
435
- ${this._effectiveTitle
436
- ? html `<header class="widget-header"><h2 class="widget-title">${this._effectiveTitle}</h2></header>`
437
- : ''}
671
+ ${headerContent}
438
672
  <div class="widget-body">
439
- ${this._error
440
- ? html `<div class="error">${this._error}</div>`
441
- : html `<div class="chart"></div>`}
442
- ${this._loading
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
443
680
  ? html `<div class="loading-overlay" aria-busy="true" aria-live="polite">
444
- <div class="loading-spinner" aria-hidden="true"></div>
445
- </div>`
681
+ <div class="loading-spinner" aria-hidden="true"></div>
682
+ </div>`
446
683
  : nothing}
447
- </div>
448
684
  ${hasWidgetFilters
449
685
  ? html `
450
686
  <analytics-widget-toolbar
@@ -12,6 +12,8 @@ export declare class TableWidget extends LitElement {
12
12
  static styles: import("lit").CSSResult;
13
13
  /** Optional title override. When unset, the widget uses the title from the API definition. */
14
14
  title: string;
15
+ /** When true, the header refresh button is hidden (e.g. when table is embedded in analytics-report which has its own refresh). */
16
+ hideRefreshButton: boolean;
15
17
  /** API URL to fetch data (e.g. /api/analytics/widgets/1/data). When widgetId + apiUrl are set, overridden by buildWidgetDataUrl(apiUrl, widgetId). */
16
18
  dataUrl: string;
17
19
  /** Widget id (used with apiUrl to build data URL: /widgets/{id}/data). */
@@ -59,6 +61,7 @@ export declare class TableWidget extends LitElement {
59
61
  private _viewportUnobserve;
60
62
  constructor();
61
63
  private _boundOnDashboardFilterChange;
64
+ private _onRefresh;
62
65
  private _boundOnDashboardRefresh;
63
66
  connectedCallback(): void;
64
67
  disconnectedCallback(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/widgets/table.ts"],"names":[],"mappings":"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"}
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,0BAuNpB;IAEF,8FAA8F;IAC1D,KAAK,EAAE,MAAM,CAAC;IAElD,kIAAkI;IAC3D,iBAAiB,EAAE,OAAO,CAAC;IAElG,sJAAsJ;IAC3F,OAAO,EAAE,MAAM,CAAC;IAE3E,0EAA0E;IAC1E,qFAAqF;IACzB,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAEtF,+FAA+F;IACrC,MAAM,EAAE,MAAM,CAAC;IAEzE,iHAAiH;IAC7E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAE1F,2GAA2G;IACvE,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAEzE,8DAA8D;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC;IAE3E,oDAAoD;IAChB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAEzF,qKAAqK;IACrK,QAAyB,WAAW,CAAyB;IAE7D,oGAAoG;IAC/D,IAAI,EAAE,OAAO,CAAC;IAEnD,uGAAuG;IACrC,cAAc,EAAE,MAAM,CAAC;IAEzF,yHAAyH;IACrF,iBAAiB,EAAE,sBAAsB,GAAG,SAAS,CAAC;IAE1F,QAAyB,QAAQ,CAAU;IAC3C,QAAyB,MAAM,CAAgB;IAC/C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,mBAAmB,CAA8C;IACzE,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,uBAAuB,CAAS;IACxC,QAAyB,gBAAgB,CAA4C;IACrF,QAAyB,cAAc,CAAqB;IAC5D,QAAyB,mBAAmB,CAA4C;IACxF,QAAyB,WAAW,CAAU;IAC9C,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,uBAAuB,CAAS;IACxC,QAAyB,gBAAgB,CAAU;IACnD,qFAAqF;IACrF,QAAyB,gBAAgB,CAAS;IAClD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,kBAAkB,CAA6B;;IAyBvD,OAAO,CAAC,6BAA6B,CAInC;IACF,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,wBAAwB,CAG9B;IAEO,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAW5B,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAuCrD,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,gBAAgB;IASf,YAAY,IAAI,IAAI;IAkB7B,sHAAsH;IACtH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAM;IAEpD,yGAAyG;IACzG,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,mBAAmB;IA2B3B,gFAAgF;IAChF,OAAO,KAAK,eAAe,GAE1B;IAED,6FAA6F;IAC7F,OAAO,CAAC,MAAM;YAQA,SAAS;IAsIvB,OAAO,CAAC,qBAAqB;IAM7B,+FAA+F;IAC/F,OAAO,CAAC,WAAW;IASnB,oEAAoE;IACpE,OAAO,CAAC,QAAQ;IAMhB,2FAA2F;IAC3F,OAAO,CAAC,eAAe;IASvB,sEAAsE;IACtE,OAAO,CAAC,eAAe;IASvB,kFAAkF;IAClF,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,YAAY;IAIX,MAAM;IAsIf,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,cAAc;IAmCtB,OAAO,CAAC,WAAW;CAKpB"}
package/widgets/table.js CHANGED
@@ -20,7 +20,6 @@ let TableWidget = class TableWidget extends LitElement {
20
20
  flex-direction: column;
21
21
  width: 100%;
22
22
  height: 100%;
23
- overflow: hidden;
24
23
  }
25
24
  table {
26
25
  width: 100%;
@@ -76,15 +75,54 @@ let TableWidget = class TableWidget extends LitElement {
76
75
  .loading-overlay {
77
76
  position: absolute;
78
77
  inset: 0;
79
- z-index: 2;
78
+ z-index: 10;
80
79
  display: flex;
81
80
  align-items: center;
82
81
  justify-content: center;
83
- background: rgba(255, 255, 255, 0.65);
82
+ background: rgba(255, 255, 255, 0.8);
84
83
  backdrop-filter: blur(4px);
85
84
  -webkit-backdrop-filter: blur(4px);
86
85
  border-radius: 4px;
87
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
+ }
88
126
  .loading-spinner {
89
127
  width: 2rem;
90
128
  height: 2rem;
@@ -105,6 +143,33 @@ let TableWidget = class TableWidget extends LitElement {
105
143
  padding: 2rem 0.75rem;
106
144
  color: #9ca3af;
107
145
  }
146
+ .btn-refresh {
147
+ flex-shrink: 0;
148
+ display: inline-flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ width: 1.75rem;
152
+ height: 1.75rem;
153
+ padding: 0;
154
+ border: none;
155
+ border-radius: 4px;
156
+ background: transparent;
157
+ color: var(--analytics-widget-header-color, #6b7280);
158
+ cursor: pointer;
159
+ transition: color 0.15s ease, background-color 0.15s ease;
160
+ }
161
+ .btn-refresh:hover {
162
+ color: var(--analytics-widget-header-color, #374151);
163
+ background: #f3f4f6;
164
+ }
165
+ .btn-refresh:disabled {
166
+ cursor: not-allowed;
167
+ opacity: 0.6;
168
+ }
169
+ .btn-refresh svg {
170
+ width: 1rem;
171
+ height: 1rem;
172
+ }
108
173
  .widget-with-toolbar {
109
174
  position: relative;
110
175
  width: 100%;
@@ -112,6 +177,7 @@ let TableWidget = class TableWidget extends LitElement {
112
177
  display: flex;
113
178
  flex-direction: column;
114
179
  min-height: 0;
180
+ --analytics-widget-toolbar-top: 2.5rem;
115
181
  }
116
182
  .widget-with-toolbar:hover analytics-widget-toolbar {
117
183
  opacity: 1;
@@ -121,12 +187,18 @@ let TableWidget = class TableWidget extends LitElement {
121
187
  flex-shrink: 0;
122
188
  padding: 0.5rem 0.75rem 0.25rem 0.75rem;
123
189
  min-width: 0;
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: space-between;
193
+ gap: 0.5rem;
194
+ background: var(--analytics-widget-header-bg, #fff);
195
+ color: var(--analytics-widget-header-color, #374151);
124
196
  }
125
197
  .widget-title {
126
198
  margin: 0;
127
199
  font-size: 0.9375rem;
128
200
  font-weight: 600;
129
- color: #374151;
201
+ color: var(--analytics-widget-header-color, #374151);
130
202
  line-height: 1.3;
131
203
  display: -webkit-box;
132
204
  -webkit-box-orient: vertical;
@@ -134,6 +206,22 @@ let TableWidget = class TableWidget extends LitElement {
134
206
  overflow: hidden;
135
207
  text-overflow: ellipsis;
136
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
+ }
137
225
  .widget-body {
138
226
  flex: 1;
139
227
  min-height: 0;
@@ -184,6 +272,11 @@ let TableWidget = class TableWidget extends LitElement {
184
272
  this._viewportVisible = true;
185
273
  this._definitionTitle = '';
186
274
  }
275
+ _onRefresh() {
276
+ if (this._loading)
277
+ return;
278
+ this._loadData();
279
+ }
187
280
  connectedCallback() {
188
281
  super.connectedCallback();
189
282
  this._attachDashboard();
@@ -261,10 +354,8 @@ let TableWidget = class TableWidget extends LitElement {
261
354
  }, {
262
355
  onEnter: () => {
263
356
  this._viewportVisible = true;
264
- if (!this._hasLoadedOnce) {
265
- this._hasLoadedOnce = true;
357
+ if (!this._hasLoadedOnce)
266
358
  this._loadData();
267
- }
268
359
  },
269
360
  onLeave: () => {
270
361
  this._viewportVisible = false;
@@ -408,6 +499,7 @@ let TableWidget = class TableWidget extends LitElement {
408
499
  this._widgetFilters = [];
409
500
  if (hideFilter !== undefined)
410
501
  this._hideFilter = hideFilter;
502
+ this._hasLoadedOnce = true;
411
503
  this._loading = false;
412
504
  if (this._loadDataPendingRefresh) {
413
505
  this._loadDataPendingRefresh = false;
@@ -451,6 +543,7 @@ let TableWidget = class TableWidget extends LitElement {
451
543
  this._widgetFilters = [];
452
544
  if (hideFilter !== undefined)
453
545
  this._hideFilter = hideFilter;
546
+ this._hasLoadedOnce = true;
454
547
  this._loading = false;
455
548
  if (this._loadDataPendingRefresh) {
456
549
  this._loadDataPendingRefresh = false;
@@ -538,6 +631,39 @@ let TableWidget = class TableWidget extends LitElement {
538
631
  const wd = this._widgetData;
539
632
  const hasHeaderRows = Boolean(wd?.header_rows?.length);
540
633
  const tableFontSizeStyle = wd?.font_size ? `font-size: ${wd.font_size}` : nothing;
634
+ const canRefresh = Boolean(this._getEffectiveDataUrl()) && !this.hideRefreshButton;
635
+ const showSkeleton = this._loading && !this._hasLoadedOnce;
636
+ const showTitlePlaceholder = !this._effectiveTitle && !this.hideRefreshButton;
637
+ const headerContent = this._effectiveTitle || canRefresh || showTitlePlaceholder
638
+ ? html `
639
+ <header class="widget-header">
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>`}
645
+ ${canRefresh
646
+ ? html `
647
+ <button
648
+ type="button"
649
+ class="btn-refresh"
650
+ title="Refresh"
651
+ aria-label="Refresh data"
652
+ ?disabled=${this._loading}
653
+ @click=${this._onRefresh}
654
+ >
655
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
656
+ <path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
657
+ <path d="M3 3v5h5" />
658
+ <path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
659
+ <path d="M16 21h5v-5" />
660
+ </svg>
661
+ </button>
662
+ `
663
+ : ''}
664
+ </header>
665
+ `
666
+ : '';
541
667
  const filterToolbar = hasWidgetFilters
542
668
  ? html `
543
669
  <analytics-widget-toolbar
@@ -548,12 +674,43 @@ let TableWidget = class TableWidget extends LitElement {
548
674
  ></analytics-widget-toolbar>
549
675
  `
550
676
  : 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 `
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 `
557
714
  <table style=${tableFontSizeStyle}>
558
715
  <thead>
559
716
  ${this._renderThead(columns, hasHeaderRows)}
@@ -565,7 +722,7 @@ let TableWidget = class TableWidget extends LitElement {
565
722
  </tbody>
566
723
  </table>
567
724
  `
568
- : html `
725
+ : html `
569
726
  <table style=${tableFontSizeStyle}>
570
727
  <thead>
571
728
  ${this._renderThead(columns, hasHeaderRows)}
@@ -577,17 +734,15 @@ let TableWidget = class TableWidget extends LitElement {
577
734
  `;
578
735
  return html `
579
736
  <div class="widget-with-toolbar">
580
- ${this._effectiveTitle
581
- ? html `<header class="widget-header"><h2 class="widget-title">${this._effectiveTitle}</h2></header>`
582
- : ''}
737
+ ${headerContent}
583
738
  <div class="widget-body">
584
739
  ${bodyContent}
585
- ${this._loading
740
+ </div>
741
+ ${showSpinnerOverlay
586
742
  ? html `<div class="loading-overlay" aria-busy="true" aria-live="polite">
587
- <div class="loading-spinner" aria-hidden="true"></div>
588
- </div>`
743
+ <div class="loading-spinner" aria-hidden="true"></div>
744
+ </div>`
589
745
  : nothing}
590
- </div>
591
746
  ${filterToolbar}
592
747
  </div>
593
748
  `;
@@ -658,6 +813,9 @@ let TableWidget = class TableWidget extends LitElement {
658
813
  __decorate([
659
814
  property({ type: String })
660
815
  ], TableWidget.prototype, "title", void 0);
816
+ __decorate([
817
+ property({ type: Boolean, attribute: 'hide-refresh-button' })
818
+ ], TableWidget.prototype, "hideRefreshButton", void 0);
661
819
  __decorate([
662
820
  property({ type: String, attribute: 'data-url' })
663
821
  ], TableWidget.prototype, "dataUrl", void 0);