@quicdata/analytics 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/analytics.css +69 -0
- package/core/auth.d.ts +19 -0
- package/core/auth.d.ts.map +1 -0
- package/core/auth.js +34 -0
- package/core/fetch-data.d.ts +115 -0
- package/core/fetch-data.d.ts.map +1 -0
- package/core/fetch-data.js +210 -0
- package/core/index.d.ts +8 -1
- package/core/index.d.ts.map +1 -1
- package/core/index.js +4 -3
- package/core/types.d.ts +250 -0
- package/core/types.d.ts.map +1 -0
- package/core/types.js +1 -0
- package/core/viewport-observer.d.ts +26 -0
- package/core/viewport-observer.d.ts.map +1 -0
- package/core/viewport-observer.js +38 -0
- package/core/widget-transform-runner.d.ts +22 -0
- package/core/widget-transform-runner.d.ts.map +1 -0
- package/core/widget-transform-runner.js +102 -0
- package/dashboard/dashboard-container.d.ts +100 -0
- package/dashboard/dashboard-container.d.ts.map +1 -0
- package/dashboard/dashboard-container.js +315 -0
- package/dashboard/index.d.ts +4 -0
- package/dashboard/index.d.ts.map +1 -0
- package/dashboard/index.js +2 -0
- package/designer/analytics-designer.d.ts +40 -0
- package/designer/analytics-designer.d.ts.map +1 -0
- package/designer/analytics-designer.js +267 -0
- package/designer/index.d.ts +5 -0
- package/designer/index.d.ts.map +1 -0
- package/designer/index.js +3 -0
- package/filters/filter-bar.d.ts +34 -0
- package/filters/filter-bar.d.ts.map +1 -0
- package/filters/filter-bar.js +233 -0
- package/filters/filter-button.d.ts +22 -0
- package/filters/filter-button.d.ts.map +1 -0
- package/filters/filter-button.js +86 -0
- package/filters/index.d.ts +7 -0
- package/filters/index.d.ts.map +1 -0
- package/filters/index.js +6 -0
- package/filters/widget-toolbar.d.ts +24 -0
- package/filters/widget-toolbar.d.ts.map +1 -0
- package/filters/widget-toolbar.js +219 -0
- package/index.d.ts +9 -1
- package/index.js +6 -1
- package/package.json +34 -9
- package/widgets/analytics-report.d.ts +49 -0
- package/widgets/analytics-report.d.ts.map +1 -0
- package/widgets/analytics-report.js +306 -0
- package/widgets/analytics-widget.d.ts +39 -0
- package/widgets/analytics-widget.d.ts.map +1 -0
- package/widgets/analytics-widget.js +230 -0
- package/widgets/bar-chart.d.ts +13 -0
- package/widgets/bar-chart.d.ts.map +1 -0
- package/widgets/bar-chart.js +77 -0
- package/widgets/base-chart.d.ts +92 -0
- package/widgets/base-chart.d.ts.map +1 -0
- package/widgets/base-chart.js +524 -0
- package/widgets/index.d.ts +13 -1
- package/widgets/index.d.ts.map +1 -1
- package/widgets/index.js +14 -3
- package/widgets/line-chart.d.ts +13 -0
- package/widgets/line-chart.d.ts.map +1 -0
- package/widgets/line-chart.js +71 -0
- package/widgets/pie-chart.d.ts +13 -0
- package/widgets/pie-chart.d.ts.map +1 -0
- package/widgets/pie-chart.js +55 -0
- package/widgets/table.d.ts +96 -0
- package/widgets/table.d.ts.map +1 -0
- package/widgets/table.js +721 -0
- package/index.d.ts.map +0 -1
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { LitElement, html, css } from 'lit';
|
|
3
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import { GridStack } from 'gridstack';
|
|
5
|
+
import '../filters/index.js';
|
|
6
|
+
import 'gridstack/dist/gridstack.min.css';
|
|
7
|
+
/** Normalized layout: children always array (possibly empty), slots always array. */
|
|
8
|
+
function getLayoutChildren(layout) {
|
|
9
|
+
return layout.children ?? [];
|
|
10
|
+
}
|
|
11
|
+
function getLayoutSlots(layout) {
|
|
12
|
+
return layout.slots ?? [];
|
|
13
|
+
}
|
|
14
|
+
export const EVENT_DASHBOARD_FILTER_CHANGE = 'analytics-dashboard-filter-change';
|
|
15
|
+
export const EVENT_DASHBOARD_REFRESH = 'analytics-dashboard-refresh';
|
|
16
|
+
export const EVENT_DASHBOARD_LAYOUT_CHANGE = 'analytics-dashboard-layout-change';
|
|
17
|
+
/**
|
|
18
|
+
* Dashboard container: renders a grid of analytics widgets (Lit web components).
|
|
19
|
+
* Shows filter button and filter bar when layout.filters is set; dispatches filter-change and refresh
|
|
20
|
+
* When layout.children is set, uses GridStack (gridster); otherwise legacy slots + CSS grid.
|
|
21
|
+
* Passes apiUrl + widgetId to widgets; dispatches layout-change when grid layout changes.
|
|
22
|
+
*/
|
|
23
|
+
let DashboardContainer = class DashboardContainer extends LitElement {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
this.layout = { slots: [] };
|
|
27
|
+
this._dashboardParams = {};
|
|
28
|
+
this._filterBarOpen = false;
|
|
29
|
+
this._refreshIntervalId = null;
|
|
30
|
+
this._gridStack = null;
|
|
31
|
+
}
|
|
32
|
+
static { this.styles = css `
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
width: 100%;
|
|
36
|
+
height: 100%;
|
|
37
|
+
}
|
|
38
|
+
.header {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
align-items: flex-start;
|
|
42
|
+
gap: 0.75rem;
|
|
43
|
+
margin-bottom: 0.75rem;
|
|
44
|
+
}
|
|
45
|
+
.header-row {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 0.75rem;
|
|
49
|
+
}
|
|
50
|
+
.grid {
|
|
51
|
+
display: grid;
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 100%;
|
|
54
|
+
min-height: 200px;
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
}
|
|
57
|
+
.grid-stack {
|
|
58
|
+
min-height: 200px;
|
|
59
|
+
width: 100%;
|
|
60
|
+
}
|
|
61
|
+
.grid-stack-item-content {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
min-height: 0;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
}
|
|
67
|
+
.slot {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-direction: column;
|
|
70
|
+
min-height: 0;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
}
|
|
73
|
+
.slot-title {
|
|
74
|
+
font-size: 0.875rem;
|
|
75
|
+
font-weight: 600;
|
|
76
|
+
padding: 0.25rem 0.5rem;
|
|
77
|
+
flex-shrink: 0;
|
|
78
|
+
}
|
|
79
|
+
.slot-chart {
|
|
80
|
+
flex: 1;
|
|
81
|
+
min-height: 0;
|
|
82
|
+
}
|
|
83
|
+
`; }
|
|
84
|
+
connectedCallback() {
|
|
85
|
+
super.connectedCallback();
|
|
86
|
+
this._startRefreshInterval();
|
|
87
|
+
}
|
|
88
|
+
disconnectedCallback() {
|
|
89
|
+
this._destroyGridStack();
|
|
90
|
+
this._stopRefreshInterval();
|
|
91
|
+
super.disconnectedCallback();
|
|
92
|
+
}
|
|
93
|
+
firstUpdated() {
|
|
94
|
+
const children = getLayoutChildren(this.layout);
|
|
95
|
+
if (children.length > 0) {
|
|
96
|
+
this._initGridStack();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
updated(changed) {
|
|
100
|
+
if (changed.has('layout')) {
|
|
101
|
+
this._stopRefreshInterval();
|
|
102
|
+
this._startRefreshInterval();
|
|
103
|
+
const children = getLayoutChildren(this.layout);
|
|
104
|
+
if (children.length > 0 && this._gridStack) {
|
|
105
|
+
this._loadGridStackChildren();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
_destroyGridStack() {
|
|
110
|
+
if (this._gridStack?.el) {
|
|
111
|
+
this._gridStack.destroy(false);
|
|
112
|
+
this._gridStack = null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
_initGridStack() {
|
|
116
|
+
const el = this.renderRoot.querySelector('.grid-stack');
|
|
117
|
+
if (!el || this._gridStack)
|
|
118
|
+
return;
|
|
119
|
+
const column = this.layout.column ?? 12;
|
|
120
|
+
this._gridStack = GridStack.init({ column, float: false }, el);
|
|
121
|
+
const saveCB = (node, w) => {
|
|
122
|
+
w.type = node.type;
|
|
123
|
+
};
|
|
124
|
+
this._gridStack.on('change', () => {
|
|
125
|
+
const saved = this._gridStack?.save(false, false, saveCB);
|
|
126
|
+
if (saved) {
|
|
127
|
+
const children = saved.map((s) => ({
|
|
128
|
+
widgetId: String(s.id ?? ''),
|
|
129
|
+
type: s.type,
|
|
130
|
+
x: s.x ?? 0,
|
|
131
|
+
y: s.y ?? 0,
|
|
132
|
+
w: s.w ?? 1,
|
|
133
|
+
h: s.h ?? 1,
|
|
134
|
+
}));
|
|
135
|
+
this.dispatchEvent(new CustomEvent(EVENT_DASHBOARD_LAYOUT_CHANGE, {
|
|
136
|
+
bubbles: true,
|
|
137
|
+
composed: true,
|
|
138
|
+
detail: { children },
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
this._loadGridStackChildren();
|
|
143
|
+
}
|
|
144
|
+
_loadGridStackChildren() {
|
|
145
|
+
const children = getLayoutChildren(this.layout);
|
|
146
|
+
if (!this._gridStack || children.length === 0)
|
|
147
|
+
return;
|
|
148
|
+
const apiUrl = this.layout.apiUrl ?? '';
|
|
149
|
+
const dashboard = this;
|
|
150
|
+
const widgets = children.map((c) => ({
|
|
151
|
+
id: c.widgetId,
|
|
152
|
+
x: c.x,
|
|
153
|
+
y: c.y,
|
|
154
|
+
w: c.w,
|
|
155
|
+
h: c.h,
|
|
156
|
+
type: c.type,
|
|
157
|
+
lazy: c.lazy,
|
|
158
|
+
prefetch_margin: c.prefetch_margin,
|
|
159
|
+
}));
|
|
160
|
+
const addRemove = (parent, w, add, grid) => {
|
|
161
|
+
if (!add || grid)
|
|
162
|
+
return undefined;
|
|
163
|
+
const widget = w;
|
|
164
|
+
const item = document.createElement('div');
|
|
165
|
+
item.className = 'grid-stack-item';
|
|
166
|
+
const content = document.createElement('div');
|
|
167
|
+
content.className = 'grid-stack-item-content';
|
|
168
|
+
const type = (widget.type || 'bar').toLowerCase();
|
|
169
|
+
const tag = type === 'line' ? 'analytics-line-chart' : type === 'pie' ? 'analytics-pie-chart' : type === 'table' ? 'analytics-table' : 'analytics-bar-chart';
|
|
170
|
+
const widgetEl = document.createElement(tag);
|
|
171
|
+
widgetEl.setAttribute('widget-id', String(widget.id ?? ''));
|
|
172
|
+
widgetEl.setAttribute('api-url', apiUrl);
|
|
173
|
+
if (widget.lazy) {
|
|
174
|
+
widgetEl.setAttribute('lazy', '');
|
|
175
|
+
const margin = widget.prefetch_margin;
|
|
176
|
+
if (margin)
|
|
177
|
+
widgetEl.setAttribute('prefetch-margin', margin);
|
|
178
|
+
}
|
|
179
|
+
widgetEl.dashboard = dashboard;
|
|
180
|
+
content.appendChild(widgetEl);
|
|
181
|
+
item.appendChild(content);
|
|
182
|
+
return item;
|
|
183
|
+
};
|
|
184
|
+
this._gridStack.load(widgets, addRemove);
|
|
185
|
+
}
|
|
186
|
+
_startRefreshInterval() {
|
|
187
|
+
const rate = this.layout.refresh_rate ?? 0;
|
|
188
|
+
if (rate > 0) {
|
|
189
|
+
this._refreshIntervalId = setInterval(() => {
|
|
190
|
+
this.dispatchEvent(new CustomEvent(EVENT_DASHBOARD_REFRESH, {
|
|
191
|
+
bubbles: true,
|
|
192
|
+
composed: true,
|
|
193
|
+
}));
|
|
194
|
+
}, rate * 1000);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
_stopRefreshInterval() {
|
|
198
|
+
if (this._refreshIntervalId != null) {
|
|
199
|
+
clearInterval(this._refreshIntervalId);
|
|
200
|
+
this._refreshIntervalId = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Current dashboard-level filter params (for widgets to merge). Returns {} when layout.hide_filter is true. */
|
|
204
|
+
getDashboardParams() {
|
|
205
|
+
if (this.layout?.hide_filter)
|
|
206
|
+
return {};
|
|
207
|
+
return { ...this._dashboardParams };
|
|
208
|
+
}
|
|
209
|
+
_onFilterChange(e) {
|
|
210
|
+
this._dashboardParams = { ...(e.detail?.params ?? {}) };
|
|
211
|
+
this.dispatchEvent(new CustomEvent(EVENT_DASHBOARD_FILTER_CHANGE, {
|
|
212
|
+
bubbles: true,
|
|
213
|
+
composed: true,
|
|
214
|
+
detail: { params: this._dashboardParams },
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
_onFilterClose() {
|
|
218
|
+
this._filterBarOpen = false;
|
|
219
|
+
}
|
|
220
|
+
_onFilterToggle() {
|
|
221
|
+
this._filterBarOpen = !this._filterBarOpen;
|
|
222
|
+
}
|
|
223
|
+
render() {
|
|
224
|
+
const children = getLayoutChildren(this.layout);
|
|
225
|
+
const slots = getLayoutSlots(this.layout);
|
|
226
|
+
const useGridster = children.length > 0;
|
|
227
|
+
const { columns = '1fr 1fr', rows = 'auto', gap = '1rem', filters = [], hide_filter = false } = this.layout;
|
|
228
|
+
const gridStyle = `grid-template-columns: ${columns}; grid-template-rows: ${rows}; gap: ${gap};`;
|
|
229
|
+
const hasFilters = filters.length > 0 && !hide_filter;
|
|
230
|
+
const activeCount = Object.keys(this._dashboardParams).filter((k) => this._dashboardParams[k] !== '' && this._dashboardParams[k] != null).length;
|
|
231
|
+
return html `
|
|
232
|
+
${hasFilters
|
|
233
|
+
? html `
|
|
234
|
+
<div class="header">
|
|
235
|
+
<div class="header-row">
|
|
236
|
+
<analytics-filter-button
|
|
237
|
+
.activeCount=${activeCount}
|
|
238
|
+
.open=${this._filterBarOpen}
|
|
239
|
+
@analytics-filter-toggle=${this._onFilterToggle}
|
|
240
|
+
></analytics-filter-button>
|
|
241
|
+
</div>
|
|
242
|
+
${this._filterBarOpen
|
|
243
|
+
? html `
|
|
244
|
+
<analytics-filter-bar
|
|
245
|
+
.filters=${filters}
|
|
246
|
+
.values=${this._dashboardParams}
|
|
247
|
+
.showClose=${true}
|
|
248
|
+
@analytics-filter-change=${this._onFilterChange}
|
|
249
|
+
@analytics-filter-close=${this._onFilterClose}
|
|
250
|
+
></analytics-filter-bar>
|
|
251
|
+
`
|
|
252
|
+
: ''}
|
|
253
|
+
</div>
|
|
254
|
+
`
|
|
255
|
+
: ''}
|
|
256
|
+
${useGridster
|
|
257
|
+
? html `<div class="grid-stack"></div>`
|
|
258
|
+
: html `
|
|
259
|
+
<div class="grid" style="${gridStyle}">
|
|
260
|
+
${slots.map((slot) => html `
|
|
261
|
+
<div class="slot" style="${slot.gridArea ? `grid-area: ${slot.gridArea};` : ''}">
|
|
262
|
+
${slot.title ? html `<div class="slot-title">${slot.title}</div>` : ''}
|
|
263
|
+
<div class="slot-chart">
|
|
264
|
+
${this._renderSlot(slot)}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
`)}
|
|
268
|
+
</div>
|
|
269
|
+
`}
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
_renderSlot(slot) {
|
|
273
|
+
const dataUrl = slot.dataUrl ?? '';
|
|
274
|
+
const params = slot.dataParams ?? {};
|
|
275
|
+
const type = (slot.type || 'bar').toLowerCase();
|
|
276
|
+
const tag = type === 'line'
|
|
277
|
+
? 'analytics-line-chart'
|
|
278
|
+
: type === 'pie'
|
|
279
|
+
? 'analytics-pie-chart'
|
|
280
|
+
: type === 'table'
|
|
281
|
+
? 'analytics-table'
|
|
282
|
+
: type === 'analytics-line-chart'
|
|
283
|
+
? 'analytics-line-chart'
|
|
284
|
+
: type === 'analytics-pie-chart'
|
|
285
|
+
? 'analytics-pie-chart'
|
|
286
|
+
: type === 'analytics-table'
|
|
287
|
+
? 'analytics-table'
|
|
288
|
+
: 'analytics-bar-chart';
|
|
289
|
+
const lazy = slot.lazy ?? false;
|
|
290
|
+
const prefetchMargin = slot.prefetch_margin ?? '200px';
|
|
291
|
+
switch (tag) {
|
|
292
|
+
case 'analytics-line-chart':
|
|
293
|
+
return html `<analytics-line-chart data-url="${dataUrl}" .dataParams=${params} .dashboard=${this} .lazy=${lazy} prefetch-margin="${prefetchMargin}"></analytics-line-chart>`;
|
|
294
|
+
case 'analytics-pie-chart':
|
|
295
|
+
return html `<analytics-pie-chart data-url="${dataUrl}" .dataParams=${params} .dashboard=${this} .lazy=${lazy} prefetch-margin="${prefetchMargin}"></analytics-pie-chart>`;
|
|
296
|
+
case 'analytics-table':
|
|
297
|
+
return html `<analytics-table data-url="${dataUrl}" .dataParams=${params} .dashboard=${this} .lazy=${lazy} prefetch-margin="${prefetchMargin}"></analytics-table>`;
|
|
298
|
+
default:
|
|
299
|
+
return html `<analytics-bar-chart data-url="${dataUrl}" .dataParams=${params} .dashboard=${this} .lazy=${lazy} prefetch-margin="${prefetchMargin}"></analytics-bar-chart>`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
__decorate([
|
|
304
|
+
property({ type: Object })
|
|
305
|
+
], DashboardContainer.prototype, "layout", void 0);
|
|
306
|
+
__decorate([
|
|
307
|
+
state()
|
|
308
|
+
], DashboardContainer.prototype, "_dashboardParams", void 0);
|
|
309
|
+
__decorate([
|
|
310
|
+
state()
|
|
311
|
+
], DashboardContainer.prototype, "_filterBarOpen", void 0);
|
|
312
|
+
DashboardContainer = __decorate([
|
|
313
|
+
customElement('analytics-dashboard')
|
|
314
|
+
], DashboardContainer);
|
|
315
|
+
export { DashboardContainer };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { DashboardContainer, EVENT_DASHBOARD_FILTER_CHANGE, EVENT_DASHBOARD_REFRESH, EVENT_DASHBOARD_LAYOUT_CHANGE, } from './dashboard-container.js';
|
|
2
|
+
export type { DashboardLayout, DashboardSlot, GridsterGridItem } from './dashboard-container.js';
|
|
3
|
+
import './dashboard-container.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../libs/analytics/src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,uBAAuB,EACvB,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEjG,OAAO,0BAA0B,CAAC"}
|
|
@@ -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 @@
|
|
|
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,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
|