@quicdata/analytics 0.0.3 → 0.0.4

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 (73) hide show
  1. package/core/auth.d.ts +19 -0
  2. package/core/auth.d.ts.map +1 -0
  3. package/core/auth.js +34 -0
  4. package/core/fetch-data.d.ts +115 -0
  5. package/core/fetch-data.d.ts.map +1 -0
  6. package/core/fetch-data.js +210 -0
  7. package/core/index.d.ts +8 -1
  8. package/core/index.d.ts.map +1 -1
  9. package/core/index.js +4 -3
  10. package/core/types.d.ts +250 -0
  11. package/core/types.d.ts.map +1 -0
  12. package/core/types.js +1 -0
  13. package/core/viewport-observer.d.ts +26 -0
  14. package/core/viewport-observer.d.ts.map +1 -0
  15. package/core/viewport-observer.js +38 -0
  16. package/core/widget-transform-runner.d.ts +22 -0
  17. package/core/widget-transform-runner.d.ts.map +1 -0
  18. package/core/widget-transform-runner.js +102 -0
  19. package/dashboard/dashboard-container.d.ts +100 -0
  20. package/dashboard/dashboard-container.d.ts.map +1 -0
  21. package/dashboard/dashboard-container.js +315 -0
  22. package/dashboard/index.d.ts +4 -0
  23. package/dashboard/index.d.ts.map +1 -0
  24. package/dashboard/index.js +2 -0
  25. package/designer/analytics-designer.d.ts +40 -0
  26. package/designer/analytics-designer.d.ts.map +1 -0
  27. package/designer/analytics-designer.js +267 -0
  28. package/designer/index.d.ts +5 -0
  29. package/designer/index.d.ts.map +1 -0
  30. package/designer/index.js +3 -0
  31. package/filters/filter-bar.d.ts +34 -0
  32. package/filters/filter-bar.d.ts.map +1 -0
  33. package/filters/filter-bar.js +233 -0
  34. package/filters/filter-button.d.ts +22 -0
  35. package/filters/filter-button.d.ts.map +1 -0
  36. package/filters/filter-button.js +86 -0
  37. package/filters/index.d.ts +7 -0
  38. package/filters/index.d.ts.map +1 -0
  39. package/filters/index.js +6 -0
  40. package/filters/widget-toolbar.d.ts +24 -0
  41. package/filters/widget-toolbar.d.ts.map +1 -0
  42. package/filters/widget-toolbar.js +216 -0
  43. package/index.d.ts +9 -1
  44. package/index.d.ts.map +1 -1
  45. package/index.js +6 -1
  46. package/package.json +14 -8
  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 +94 -0
  57. package/widgets/base-chart.d.ts.map +1 -0
  58. package/widgets/base-chart.js +535 -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 +101 -0
  69. package/widgets/table.d.ts.map +1 -0
  70. package/widgets/table.js +740 -0
  71. package/workers/widget-transform.worker.d.ts +21 -0
  72. package/workers/widget-transform.worker.d.ts.map +1 -0
  73. package/workers/widget-transform.worker.js +30 -0
@@ -0,0 +1,40 @@
1
+ import { LitElement } from 'lit';
2
+ import type { DashboardLayout } from '../dashboard/dashboard-container.js';
3
+ export interface DesignerWidgetOption {
4
+ type: string;
5
+ label: string;
6
+ }
7
+ /**
8
+ * Designer: drag-drop widget types onto a grid, set data-url per slot, emit layout.
9
+ * Use with analytics-dashboard to render the saved layout.
10
+ */
11
+ export declare class AnalyticsDesigner extends LitElement {
12
+ static styles: import("lit").CSSResult;
13
+ widgetOptions: DesignerWidgetOption[];
14
+ /** Base URL for widget data (e.g. /api/analytics). */
15
+ baseUrl: string;
16
+ private _slots;
17
+ private _dragOverId;
18
+ private _draggingType;
19
+ private _idCounter;
20
+ render(): import("lit-html").TemplateResult<1>;
21
+ private _typeLabel;
22
+ private _renderSlotPreview;
23
+ private _onPaletteDragStart;
24
+ private _onGridDragOver;
25
+ private _onGridDragLeave;
26
+ private _onGridDrop;
27
+ private _onSlotDrop;
28
+ private _addSlot;
29
+ private _removeSlot;
30
+ private _onSlotUrlChange;
31
+ private _emitLayout;
32
+ /** Set layout from outside (e.g. loaded from backend). */
33
+ setLayout(layout: DashboardLayout): void;
34
+ }
35
+ declare global {
36
+ interface HTMLElementTagNameMap {
37
+ 'analytics-designer': AnalyticsDesigner;
38
+ }
39
+ }
40
+ //# sourceMappingURL=analytics-designer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics-designer.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/designer/analytics-designer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAE5C,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,qCAAqC,CAAC;AAE1F,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAQD;;;GAGG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAC/C,OAAgB,MAAM,0BAsGpB;IAEyB,aAAa,EAAE,oBAAoB,EAAE,CAA0B;IAE1F,sDAAsD;IAC1B,OAAO,SAAM;IAEhC,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,WAAW,CAAuB;IACnD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,UAAU,CAAK;IAEd,MAAM;IAsDf,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,kBAAkB;IAa1B,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,WAAW;IAUnB,0DAA0D;IAC1D,SAAS,CAAC,MAAM,EAAE,eAAe;CAOlC;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF"}
@@ -0,0 +1,267 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html, css } from 'lit';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ const DEFAULT_WIDGET_OPTIONS = [
5
+ { type: 'bar', label: 'Bar chart' },
6
+ { type: 'line', label: 'Line chart' },
7
+ { type: 'pie', label: 'Pie chart' },
8
+ ];
9
+ /**
10
+ * Designer: drag-drop widget types onto a grid, set data-url per slot, emit layout.
11
+ * Use with analytics-dashboard to render the saved layout.
12
+ */
13
+ let AnalyticsDesigner = class AnalyticsDesigner extends LitElement {
14
+ constructor() {
15
+ super(...arguments);
16
+ this.widgetOptions = DEFAULT_WIDGET_OPTIONS;
17
+ /** Base URL for widget data (e.g. /api/analytics). */
18
+ this.baseUrl = '';
19
+ this._slots = [];
20
+ this._dragOverId = null;
21
+ this._draggingType = null;
22
+ this._idCounter = 0;
23
+ }
24
+ static { this.styles = css `
25
+ :host {
26
+ display: flex;
27
+ width: 100%;
28
+ height: 100%;
29
+ min-height: 400px;
30
+ }
31
+ .palette {
32
+ width: 180px;
33
+ flex-shrink: 0;
34
+ padding: 0.5rem;
35
+ border-right: 1px solid #ddd;
36
+ background: #f8f9fa;
37
+ }
38
+ .palette-title {
39
+ font-size: 0.75rem;
40
+ font-weight: 600;
41
+ text-transform: uppercase;
42
+ color: #666;
43
+ margin-bottom: 0.5rem;
44
+ }
45
+ .palette-item {
46
+ padding: 0.5rem 0.75rem;
47
+ margin-bottom: 0.25rem;
48
+ background: #fff;
49
+ border: 1px solid #dee2e6;
50
+ border-radius: 4px;
51
+ cursor: grab;
52
+ font-size: 0.875rem;
53
+ user-select: none;
54
+ }
55
+ .palette-item:hover {
56
+ border-color: #0d6efd;
57
+ background: #f0f7ff;
58
+ }
59
+ .palette-item:active {
60
+ cursor: grabbing;
61
+ }
62
+ .canvas {
63
+ flex: 1;
64
+ padding: 1rem;
65
+ overflow: auto;
66
+ background: #fff;
67
+ }
68
+ .grid {
69
+ display: grid;
70
+ grid-template-columns: repeat(4, 1fr);
71
+ grid-auto-rows: 160px;
72
+ gap: 1rem;
73
+ min-height: 300px;
74
+ }
75
+ .slot {
76
+ border: 2px dashed #dee2e6;
77
+ border-radius: 8px;
78
+ padding: 0.5rem;
79
+ display: flex;
80
+ flex-direction: column;
81
+ background: #fafafa;
82
+ }
83
+ .slot.drag-over {
84
+ border-color: #0d6efd;
85
+ background: #f0f7ff;
86
+ }
87
+ .slot-header {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 0.5rem;
91
+ margin-bottom: 0.25rem;
92
+ font-size: 0.75rem;
93
+ }
94
+ .slot-type {
95
+ font-weight: 600;
96
+ color: #333;
97
+ }
98
+ .slot-url {
99
+ flex: 1;
100
+ min-width: 0;
101
+ padding: 0.2rem 0.4rem;
102
+ font-size: 0.7rem;
103
+ border: 1px solid #dee2e6;
104
+ border-radius: 4px;
105
+ }
106
+ .slot-preview {
107
+ flex: 1;
108
+ min-height: 0;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ color: #999;
113
+ font-size: 0.75rem;
114
+ }
115
+ .slot-remove {
116
+ padding: 0.1rem 0.4rem;
117
+ font-size: 0.7rem;
118
+ cursor: pointer;
119
+ color: #dc3545;
120
+ border: none;
121
+ background: none;
122
+ }
123
+ .slot-remove:hover {
124
+ text-decoration: underline;
125
+ }
126
+ `; }
127
+ render() {
128
+ return html `
129
+ <div class="palette">
130
+ <div class="palette-title">Widgets</div>
131
+ ${this.widgetOptions.map((w) => html `
132
+ <div
133
+ class="palette-item"
134
+ draggable="true"
135
+ @dragstart=${(e) => this._onPaletteDragStart(e, w.type)}
136
+ @dragend=${() => (this._draggingType = null)}
137
+ >
138
+ ${w.label}
139
+ </div>
140
+ `)}
141
+ </div>
142
+ <div class="canvas">
143
+ <div
144
+ class="grid"
145
+ @dragover=${this._onGridDragOver}
146
+ @dragleave=${this._onGridDragLeave}
147
+ @drop=${this._onGridDrop}
148
+ >
149
+ ${this._slots.map((slot) => html `
150
+ <div
151
+ class="slot ${this._dragOverId === slot.id ? 'drag-over' : ''}"
152
+ data-slot-id="${slot.id}"
153
+ @dragover=${(e) => e.preventDefault()}
154
+ @drop=${(e) => this._onSlotDrop(e, slot.id)}
155
+ >
156
+ <div class="slot-header">
157
+ <span class="slot-type">${this._typeLabel(slot.type)}</span>
158
+ <input
159
+ class="slot-url"
160
+ type="text"
161
+ placeholder="Data URL"
162
+ .value=${slot.dataUrl ?? ''}
163
+ @input=${(e) => this._onSlotUrlChange(slot.id, e.target.value)}
164
+ />
165
+ <button type="button" class="slot-remove" @click=${() => this._removeSlot(slot.id)}>Remove</button>
166
+ </div>
167
+ <div class="slot-preview">
168
+ ${this._renderSlotPreview(slot)}
169
+ </div>
170
+ </div>
171
+ `)}
172
+ </div>
173
+ </div>
174
+ `;
175
+ }
176
+ _typeLabel(type) {
177
+ return this.widgetOptions.find((w) => w.type === type)?.label ?? type;
178
+ }
179
+ _renderSlotPreview(slot) {
180
+ const dataUrl = slot.dataUrl ?? '';
181
+ const params = slot.dataParams ?? {};
182
+ const type = (slot.type || 'bar').toLowerCase();
183
+ if (type === 'line') {
184
+ return html `<analytics-line-chart data-url="${dataUrl}" .dataParams=${params} style="width:100%;height:100%;min-height:80px"></analytics-line-chart>`;
185
+ }
186
+ if (type === 'pie') {
187
+ return html `<analytics-pie-chart data-url="${dataUrl}" .dataParams=${params} style="width:100%;height:100%;min-height:80px"></analytics-pie-chart>`;
188
+ }
189
+ return html `<analytics-bar-chart data-url="${dataUrl}" .dataParams=${params} style="width:100%;height:100%;min-height:80px"></analytics-bar-chart>`;
190
+ }
191
+ _onPaletteDragStart(e, type) {
192
+ this._draggingType = type;
193
+ e.dataTransfer?.setData('text/plain', type);
194
+ e.dataTransfer.effectAllowed = 'copy';
195
+ }
196
+ _onGridDragOver(e) {
197
+ e.preventDefault();
198
+ e.dataTransfer.dropEffect = 'copy';
199
+ }
200
+ _onGridDragLeave() {
201
+ this._dragOverId = null;
202
+ }
203
+ _onGridDrop(e) {
204
+ e.preventDefault();
205
+ this._dragOverId = null;
206
+ const type = e.dataTransfer?.getData('text/plain') || this._draggingType;
207
+ if (type) {
208
+ this._addSlot(type);
209
+ }
210
+ }
211
+ _onSlotDrop(e, _slotId) {
212
+ e.preventDefault();
213
+ e.stopPropagation();
214
+ this._dragOverId = null;
215
+ const type = e.dataTransfer?.getData('text/plain') || this._draggingType;
216
+ if (type) {
217
+ this._addSlot(type);
218
+ }
219
+ }
220
+ _addSlot(type) {
221
+ const id = `slot-${++this._idCounter}`;
222
+ const dataUrl = this.baseUrl ? `${this.baseUrl.replace(/\/$/, '')}/widgets/0/data` : '';
223
+ this._slots = [...this._slots, { id, type, dataUrl, dataParams: {} }];
224
+ this._emitLayout();
225
+ }
226
+ _removeSlot(id) {
227
+ this._slots = this._slots.filter((s) => s.id !== id);
228
+ this._emitLayout();
229
+ }
230
+ _onSlotUrlChange(id, value) {
231
+ this._slots = this._slots.map((s) => (s.id === id ? { ...s, dataUrl: value } : s));
232
+ this._emitLayout();
233
+ }
234
+ _emitLayout() {
235
+ const layout = {
236
+ columns: 'repeat(4, 1fr)',
237
+ rows: 'auto',
238
+ gap: '1rem',
239
+ slots: this._slots.map(({ id: _id, ...slot }) => slot),
240
+ };
241
+ this.dispatchEvent(new CustomEvent('layout-change', { detail: layout, bubbles: true, composed: true }));
242
+ }
243
+ /** Set layout from outside (e.g. loaded from backend). */
244
+ setLayout(layout) {
245
+ this._slots = (layout.slots ?? []).map((s, i) => ({
246
+ ...s,
247
+ id: `slot-${++this._idCounter}-${i}`,
248
+ }));
249
+ this._emitLayout();
250
+ }
251
+ };
252
+ __decorate([
253
+ property({ type: Array })
254
+ ], AnalyticsDesigner.prototype, "widgetOptions", void 0);
255
+ __decorate([
256
+ property({ type: String })
257
+ ], AnalyticsDesigner.prototype, "baseUrl", void 0);
258
+ __decorate([
259
+ state()
260
+ ], AnalyticsDesigner.prototype, "_slots", void 0);
261
+ __decorate([
262
+ state()
263
+ ], AnalyticsDesigner.prototype, "_dragOverId", void 0);
264
+ AnalyticsDesigner = __decorate([
265
+ customElement('analytics-designer')
266
+ ], AnalyticsDesigner);
267
+ export { AnalyticsDesigner };
@@ -0,0 +1,5 @@
1
+ export { AnalyticsDesigner } from './analytics-designer.js';
2
+ export type { DesignerWidgetOption } from './analytics-designer.js';
3
+ import '../widgets/index.js';
4
+ import './analytics-designer.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/designer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEpE,OAAO,qBAAqB,CAAC;AAC7B,OAAO,yBAAyB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { AnalyticsDesigner } from './analytics-designer.js';
2
+ import '../widgets/index.js';
3
+ import './analytics-designer.js';
@@ -0,0 +1,34 @@
1
+ import { LitElement } from 'lit';
2
+ import type { FilterDefinition } from '../core/types.js';
3
+ export declare const EVENT_FILTER_CHANGE = "analytics-filter-change";
4
+ export declare const EVENT_FILTER_CLOSE = "analytics-filter-close";
5
+ /**
6
+ * Filter bar: renders a row of filter controls from filter definitions.
7
+ * Emits analytics-filter-change when any value changes; analytics-filter-close when close is clicked.
8
+ */
9
+ export declare class AnalyticsFilterBar extends LitElement {
10
+ static styles: import("lit").CSSResult;
11
+ /** Filter definitions (param_name, type, options). */
12
+ filters: FilterDefinition[];
13
+ /** Current filter values keyed by param_name. */
14
+ values: Record<string, string | number | boolean>;
15
+ /** Whether to show the close button. */
16
+ showClose: boolean;
17
+ /** Build params to send: use default_value only when param was never set (undefined). User input or Clear sets explicit value (including ''), so we do not use default_value then. */
18
+ private _effectiveParams;
19
+ private _emitChange;
20
+ private _onInput;
21
+ private _onClear;
22
+ private _onClose;
23
+ private _label;
24
+ /** Display value: show default_value only when param was never set (rerender). After user input or Clear, show the actual value (including ''). */
25
+ private _effectiveValue;
26
+ private _renderControl;
27
+ render(): import("lit-html").TemplateResult<1>;
28
+ }
29
+ declare global {
30
+ interface HTMLElementTagNameMap {
31
+ 'analytics-filter-bar': AnalyticsFilterBar;
32
+ }
33
+ }
34
+ //# sourceMappingURL=filter-bar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-bar.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/filters/filter-bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAE5C,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,kBAAkB,CAAC;AAEvE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAE3D;;;GAGG;AACH,qBACa,kBAAmB,SAAQ,UAAU;IAChD,OAAgB,MAAM,0BAsEpB;IAEF,sDAAsD;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAM;IAE5D,iDAAiD;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAM;IAEnF,wCAAwC;IACX,SAAS,UAAQ;IAE9C,sLAAsL;IACtL,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,MAAM;IAId,mJAAmJ;IACnJ,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,cAAc;IA2Eb,MAAM;CAgBhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,sBAAsB,EAAE,kBAAkB,CAAC;KAC5C;CACF"}
@@ -0,0 +1,233 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html, css } from 'lit';
3
+ import { customElement, property } from 'lit/decorators.js';
4
+ export const EVENT_FILTER_CHANGE = 'analytics-filter-change';
5
+ export const EVENT_FILTER_CLOSE = 'analytics-filter-close';
6
+ /**
7
+ * Filter bar: renders a row of filter controls from filter definitions.
8
+ * Emits analytics-filter-change when any value changes; analytics-filter-close when close is clicked.
9
+ */
10
+ let AnalyticsFilterBar = class AnalyticsFilterBar extends LitElement {
11
+ constructor() {
12
+ super(...arguments);
13
+ /** Filter definitions (param_name, type, options). */
14
+ this.filters = [];
15
+ /** Current filter values keyed by param_name. */
16
+ this.values = {};
17
+ /** Whether to show the close button. */
18
+ this.showClose = true;
19
+ }
20
+ static { this.styles = css `
21
+ :host {
22
+ display: block;
23
+ }
24
+ .bar {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ align-items: center;
28
+ gap: 0.75rem 1rem;
29
+ padding: 0.75rem 1rem;
30
+ background: var(--analytics-filter-bar-bg, transparent);
31
+ border: none;
32
+ border-radius: 0.5rem;
33
+ }
34
+ .group {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 0.5rem;
38
+ }
39
+ label {
40
+ font-size: 0.8125rem;
41
+ font-weight: 500;
42
+ color: var(--analytics-filter-bar-label-color, #475569);
43
+ white-space: nowrap;
44
+ }
45
+ select,
46
+ input[type='text'],
47
+ input[type='number'],
48
+ input[type='date'] {
49
+ padding: 0.375rem 0.5rem;
50
+ font-size: 0.875rem;
51
+ border: 1px solid var(--analytics-filter-bar-input-border, #cbd5e1);
52
+ border-radius: 0.375rem;
53
+ min-width: 8rem;
54
+ background: var(--analytics-filter-bar-input-bg, #fff);
55
+ color: var(--analytics-filter-bar-input-color, inherit);
56
+ }
57
+ select:focus,
58
+ input:focus {
59
+ outline: none;
60
+ border-color: #3b82f6;
61
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
62
+ }
63
+ .actions {
64
+ margin-left: auto;
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ }
69
+ .btn {
70
+ padding: 0.375rem 0.75rem;
71
+ font-size: 0.8125rem;
72
+ border-radius: 0.375rem;
73
+ cursor: pointer;
74
+ border: 1px solid var(--analytics-filter-bar-border, #e2e8f0);
75
+ background: transparent;
76
+ color: var(--analytics-filter-bar-close-color, #64748b);
77
+ }
78
+ .btn:hover {
79
+ background: var(--analytics-filter-bar-close-hover-bg, #f1f5f9);
80
+ color: var(--analytics-filter-bar-close-hover-color, #334155);
81
+ }
82
+ .btn-clear {
83
+ color: #0ea5e9;
84
+ border-color: rgba(14, 165, 233, 0.4);
85
+ }
86
+ .btn-clear:hover {
87
+ background: rgba(14, 165, 233, 0.08);
88
+ color: #0284c7;
89
+ }
90
+ `; }
91
+ /** Build params to send: use default_value only when param was never set (undefined). User input or Clear sets explicit value (including ''), so we do not use default_value then. */
92
+ _effectiveParams() {
93
+ const out = {};
94
+ for (const def of this.filters) {
95
+ const current = this.values[def.param_name];
96
+ const neverSet = !(def.param_name in this.values) || current === undefined;
97
+ out[def.param_name] = neverSet ? (def.default_value ?? '') : (current ?? '');
98
+ }
99
+ return out;
100
+ }
101
+ _emitChange() {
102
+ this.dispatchEvent(new CustomEvent(EVENT_FILTER_CHANGE, {
103
+ bubbles: true,
104
+ composed: true,
105
+ detail: { params: this._effectiveParams() },
106
+ }));
107
+ }
108
+ _onInput(paramName, value) {
109
+ this.values = { ...this.values, [paramName]: value };
110
+ this._emitChange();
111
+ }
112
+ _onClear() {
113
+ const empty = {};
114
+ for (const def of this.filters) {
115
+ empty[def.param_name] = '';
116
+ }
117
+ this.values = empty;
118
+ this._emitChange();
119
+ }
120
+ _onClose() {
121
+ this.dispatchEvent(new CustomEvent(EVENT_FILTER_CLOSE, {
122
+ bubbles: true,
123
+ composed: true,
124
+ }));
125
+ }
126
+ _label(def) {
127
+ return (def.label != null && def.label !== '') ? def.label : def.param_name;
128
+ }
129
+ /** Display value: show default_value only when param was never set (rerender). After user input or Clear, show the actual value (including ''). */
130
+ _effectiveValue(def) {
131
+ const v = this.values[def.param_name];
132
+ const neverSet = !(def.param_name in this.values) || v === undefined;
133
+ if (neverSet)
134
+ return def.default_value ?? '';
135
+ return v ?? '';
136
+ }
137
+ _renderControl(def) {
138
+ const value = this._effectiveValue(def);
139
+ const options = (def.options ?? []);
140
+ const label = this._label(def);
141
+ if (def.type === 'preset' || (options.length > 0 && ['select', 'dropdown'].includes(def.type))) {
142
+ const current = (value !== '' && value != null ? String(value) : '');
143
+ return html `
144
+ <div class="group">
145
+ <label for="filter-${def.param_name}">${label}</label>
146
+ <select
147
+ id="filter-${def.param_name}"
148
+ .value=${current}
149
+ @change=${(e) => {
150
+ const v = e.target.value;
151
+ const opt = options.find((o) => String(o.value) === v);
152
+ this._onInput(def.param_name, opt != null ? opt.value : v);
153
+ }}
154
+ >
155
+ <option value="">—</option>
156
+ ${options.map((o) => html `<option value="${String(o.value)}" .selected=${String(o.value) === current}>
157
+ ${o.label ?? String(o.value)}
158
+ </option>`)}
159
+ </select>
160
+ </div>
161
+ `;
162
+ }
163
+ if (def.type === 'number') {
164
+ const num = typeof value === 'number' ? value : value !== '' && value != null ? Number(value) : (def.default_value != null ? Number(def.default_value) : '');
165
+ return html `
166
+ <div class="group">
167
+ <label for="filter-${def.param_name}">${label}</label>
168
+ <input
169
+ type="number"
170
+ id="filter-${def.param_name}"
171
+ .value=${num}
172
+ @input=${(e) => this._onInput(def.param_name, Number(e.target.value) || 0)}
173
+ />
174
+ </div>
175
+ `;
176
+ }
177
+ if (def.type === 'date') {
178
+ const str = (value != null && value !== '' ? String(value) : (def.default_value != null ? String(def.default_value) : ''));
179
+ return html `
180
+ <div class="group">
181
+ <label for="filter-${def.param_name}">${label}</label>
182
+ <input
183
+ type="date"
184
+ id="filter-${def.param_name}"
185
+ .value=${str}
186
+ @input=${(e) => this._onInput(def.param_name, e.target.value)}
187
+ />
188
+ </div>
189
+ `;
190
+ }
191
+ const str = (value != null && value !== '' ? String(value) : (def.default_value != null ? String(def.default_value) : ''));
192
+ return html `
193
+ <div class="group">
194
+ <label for="filter-${def.param_name}">${label}</label>
195
+ <input
196
+ type="text"
197
+ id="filter-${def.param_name}"
198
+ .value=${str}
199
+ @input=${(e) => this._onInput(def.param_name, e.target.value)}
200
+ />
201
+ </div>
202
+ `;
203
+ }
204
+ render() {
205
+ if (this.filters.length === 0) {
206
+ return html `<div class="bar"><span style="color:#64748b;font-size:0.875rem">No filters</span></div>`;
207
+ }
208
+ return html `
209
+ <div class="bar">
210
+ ${this.filters.map((def) => this._renderControl(def))}
211
+ <div class="actions">
212
+ <button type="button" class="btn btn-clear" @click=${this._onClear} title="Reset all filters to default">Clear</button>
213
+ ${this.showClose
214
+ ? html `<button type="button" class="btn" @click=${this._onClose} title="Close" aria-label="Close">×</button>`
215
+ : ''}
216
+ </div>
217
+ </div>
218
+ `;
219
+ }
220
+ };
221
+ __decorate([
222
+ property({ type: Array })
223
+ ], AnalyticsFilterBar.prototype, "filters", void 0);
224
+ __decorate([
225
+ property({ type: Object })
226
+ ], AnalyticsFilterBar.prototype, "values", void 0);
227
+ __decorate([
228
+ property({ type: Boolean })
229
+ ], AnalyticsFilterBar.prototype, "showClose", void 0);
230
+ AnalyticsFilterBar = __decorate([
231
+ customElement('analytics-filter-bar')
232
+ ], AnalyticsFilterBar);
233
+ export { AnalyticsFilterBar };
@@ -0,0 +1,22 @@
1
+ import { LitElement } from 'lit';
2
+ export declare const EVENT_FILTER_TOGGLE = "analytics-filter-toggle";
3
+ /**
4
+ * Filter button: toggles filter bar visibility.
5
+ * Dispatches analytics-filter-toggle when clicked; parent should show/hide the filter bar.
6
+ * Optionally shows a badge when activeCount > 0.
7
+ */
8
+ export declare class AnalyticsFilterButton extends LitElement {
9
+ static styles: import("lit").CSSResult;
10
+ /** Number of active filters (shows badge when > 0). */
11
+ activeCount: number;
12
+ /** Whether the filter bar is currently open (button appears active). */
13
+ open: boolean;
14
+ private _onClick;
15
+ render(): import("lit-html").TemplateResult<1>;
16
+ }
17
+ declare global {
18
+ interface HTMLElementTagNameMap {
19
+ 'analytics-filter-button': AnalyticsFilterButton;
20
+ }
21
+ }
22
+ //# sourceMappingURL=filter-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-button.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/filters/filter-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D;;;;GAIG;AACH,qBACa,qBAAsB,SAAQ,UAAU;IACnD,OAAgB,MAAM,0BAqCpB;IAEF,uDAAuD;IAC3B,WAAW,SAAK;IAE5C,wEAAwE;IAC3C,IAAI,UAAS;IAE1C,OAAO,CAAC,QAAQ;IASP,MAAM;CAchB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,yBAAyB,EAAE,qBAAqB,CAAC;KAClD;CACF"}