@nubitio/dashboard 0.5.19

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johan Guerreros
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,537 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _nubitio_ui = require("@nubitio/ui");
3
+ let react = require("react");
4
+ let _nubitio_core = require("@nubitio/core");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ let react_router_dom = require("react-router-dom");
7
+ //#region packages/dashboard/dashboard/defineDashboard.ts
8
+ const dashboardCache = /* @__PURE__ */ new Map();
9
+ function cacheKey(config) {
10
+ try {
11
+ return JSON.stringify({
12
+ id: config.id,
13
+ title: config.title,
14
+ dataUrl: config.dataUrl,
15
+ sections: config.sections
16
+ });
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * Declarative dashboard definition — mirrors `defineResource` ergonomics.
23
+ *
24
+ * ```ts
25
+ * const overview = defineDashboard({
26
+ * title: 'Dashboard',
27
+ * dataUrl: '/api/dashboard/overview',
28
+ * sections: [
29
+ * { layout: 'stats', widgets: [statWidget({ id: 'revenue', title: 'Revenue', valuePath: 'stats.revenue', format: 'currency' })] },
30
+ * ],
31
+ * });
32
+ *
33
+ * export const DashboardRoute = () => <DashboardPage config={overview} />;
34
+ * ```
35
+ */
36
+ function defineDashboard(config) {
37
+ const key = cacheKey(config);
38
+ if (key !== null && dashboardCache.has(key)) return dashboardCache.get(key);
39
+ const dashboard = {
40
+ id: "dashboard",
41
+ ...config
42
+ };
43
+ if (key !== null) dashboardCache.set(key, dashboard);
44
+ return dashboard;
45
+ }
46
+ //#endregion
47
+ //#region packages/dashboard/dashboard/useDashboardData.ts
48
+ function resolveDataUrl(dataUrl) {
49
+ if (/^https?:\/\//i.test(dataUrl)) return dataUrl;
50
+ return `${(0, _nubitio_core.getCoreApiBaseUrl)().replace(/\/+$/, "")}${dataUrl.startsWith("/") ? dataUrl : `/${dataUrl}`}`;
51
+ }
52
+ const EMPTY = {
53
+ data: {},
54
+ loading: false,
55
+ error: null
56
+ };
57
+ function useDashboardData(dataUrl, refreshInterval) {
58
+ const [state, setState] = (0, react.useState)({
59
+ ...EMPTY,
60
+ loading: !!dataUrl
61
+ });
62
+ const refetch = (0, react.useCallback)(async () => {
63
+ if (!dataUrl) {
64
+ setState(EMPTY);
65
+ return;
66
+ }
67
+ setState((prev) => ({
68
+ ...prev,
69
+ loading: true,
70
+ error: null
71
+ }));
72
+ try {
73
+ const response = await fetch(resolveDataUrl(dataUrl), { credentials: "include" });
74
+ if (!response.ok) throw new Error(`Dashboard request failed (${response.status})`);
75
+ setState({
76
+ data: await response.json(),
77
+ loading: false,
78
+ error: null
79
+ });
80
+ } catch (error) {
81
+ setState({
82
+ data: {},
83
+ loading: false,
84
+ error: error instanceof Error ? error.message : "Failed to load dashboard"
85
+ });
86
+ }
87
+ }, [dataUrl]);
88
+ (0, react.useEffect)(() => {
89
+ refetch();
90
+ }, [refetch]);
91
+ (0, react.useEffect)(() => {
92
+ if (!refreshInterval || !dataUrl) return void 0;
93
+ const timer = window.setInterval(() => {
94
+ refetch();
95
+ }, refreshInterval);
96
+ return () => window.clearInterval(timer);
97
+ }, [
98
+ dataUrl,
99
+ refreshInterval,
100
+ refetch
101
+ ]);
102
+ return {
103
+ ...state,
104
+ refetch
105
+ };
106
+ }
107
+ //#endregion
108
+ //#region packages/dashboard/dashboard/formatValue.ts
109
+ function formatDashboardValue(value, format = "text") {
110
+ if (value === null || value === void 0) return "—";
111
+ if (format === "text") return String(value);
112
+ if (format === "date" || format === "datetime") {
113
+ const date = value instanceof Date ? value : new Date(String(value));
114
+ if (Number.isNaN(date.getTime())) return String(value);
115
+ return new Intl.DateTimeFormat((0, _nubitio_core.getCoreLocale)(), {
116
+ dateStyle: format === "date" ? "medium" : void 0,
117
+ timeStyle: format === "datetime" ? "short" : void 0
118
+ }).format(date);
119
+ }
120
+ const numeric = typeof value === "number" ? value : Number(value);
121
+ if (!Number.isFinite(numeric)) return String(value);
122
+ const baseOptions = {
123
+ minimumFractionDigits: format === "currency" ? 2 : void 0,
124
+ maximumFractionDigits: format === "currency" ? 2 : void 0
125
+ };
126
+ if (format === "currency") {
127
+ const currency = (0, _nubitio_core.getCoreCurrency)();
128
+ if (!currency) return new Intl.NumberFormat((0, _nubitio_core.getCoreLocale)(), baseOptions).format(numeric);
129
+ return new Intl.NumberFormat((0, _nubitio_core.getCoreLocale)(), {
130
+ ...baseOptions,
131
+ style: "currency",
132
+ currency,
133
+ currencyDisplay: "narrowSymbol"
134
+ }).format(numeric);
135
+ }
136
+ if (format === "percent") {
137
+ const normalized = Math.abs(numeric) <= 1 ? numeric : numeric / 100;
138
+ return new Intl.NumberFormat((0, _nubitio_core.getCoreLocale)(), {
139
+ style: "percent",
140
+ minimumFractionDigits: 1,
141
+ maximumFractionDigits: 1
142
+ }).format(normalized);
143
+ }
144
+ return new Intl.NumberFormat((0, _nubitio_core.getCoreLocale)()).format(numeric);
145
+ }
146
+ //#endregion
147
+ //#region packages/dashboard/dashboard/resolvePath.ts
148
+ function resolvePath(source, path) {
149
+ if (path === void 0 || path === "") return source;
150
+ if (source === null || source === void 0) return void 0;
151
+ return path.split(".").reduce((current, segment) => {
152
+ if (current === null || current === void 0) return void 0;
153
+ if (typeof current !== "object") return void 0;
154
+ return current[segment];
155
+ }, source);
156
+ }
157
+ function resolveArray(source, path) {
158
+ const value = resolvePath(source, path);
159
+ if (!Array.isArray(value)) return [];
160
+ return value.filter((item) => item !== null && typeof item === "object");
161
+ }
162
+ //#endregion
163
+ //#region packages/dashboard/dashboard/widgets/BarChartWidgetView.tsx
164
+ function toNumber$1(value) {
165
+ const numeric = typeof value === "number" ? value : Number(value);
166
+ return Number.isFinite(numeric) ? numeric : 0;
167
+ }
168
+ function BarChartWidgetView({ widget, data, loading }) {
169
+ const rows = resolveArray(data, widget.dataPath);
170
+ const max = rows.reduce((peak, row) => Math.max(peak, toNumber$1(row[widget.yKey])), 0) || 1;
171
+ const height = widget.height ?? 200;
172
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.StatCard, {
173
+ title: widget.title,
174
+ headerExtra: widget.subtitle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
175
+ className: "nb-dashboard-widget__subtitle",
176
+ children: widget.subtitle
177
+ }) : void 0,
178
+ menuVisible: widget.menuVisible,
179
+ isLoading: loading,
180
+ className: "nb-dashboard-chart-card",
181
+ children: rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
182
+ className: "nb-dashboard-chart-empty",
183
+ children: "No data"
184
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
185
+ className: "nb-dashboard-bar-chart",
186
+ style: { height },
187
+ role: "img",
188
+ "aria-label": widget.title,
189
+ children: rows.map((row, index) => {
190
+ const label = String(row[widget.xKey] ?? "");
191
+ const value = toNumber$1(row[widget.yKey]);
192
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
193
+ className: "nb-dashboard-bar-chart__item",
194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
195
+ className: "nb-dashboard-bar-chart__bar-wrap",
196
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
197
+ className: "nb-dashboard-bar-chart__bar",
198
+ style: { height: `${Math.max(4, value / max * 100)}%` },
199
+ title: `${label}: ${formatDashboardValue(value, widget.valueFormat)}`
200
+ })
201
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
202
+ className: "nb-dashboard-bar-chart__label",
203
+ children: label
204
+ })]
205
+ }, `${label}-${index}`);
206
+ })
207
+ })
208
+ });
209
+ }
210
+ //#endregion
211
+ //#region packages/dashboard/dashboard/widgets/DonutChartWidgetView.tsx
212
+ const DEFAULT_COLORS = [
213
+ "var(--accent-color)",
214
+ "var(--success-color)",
215
+ "var(--warning-color)",
216
+ "var(--info-color)",
217
+ "#6b4fc8",
218
+ "#c2410c"
219
+ ];
220
+ function toNumber(value) {
221
+ const numeric = typeof value === "number" ? value : Number(value);
222
+ return Number.isFinite(numeric) ? numeric : 0;
223
+ }
224
+ function DonutChartWidgetView({ widget, data, loading }) {
225
+ const rows = resolveArray(data, widget.dataPath);
226
+ const colors = widget.colors ?? DEFAULT_COLORS;
227
+ const total = rows.reduce((sum, row) => sum + toNumber(row[widget.valueKey]), 0);
228
+ const centerValue = widget.centerValuePath !== void 0 ? resolvePath(data, widget.centerValuePath) : total;
229
+ const radius = 38;
230
+ const stroke = 14;
231
+ const circumference = 2 * Math.PI * radius;
232
+ const arcs = rows.reduce((segments, row, index) => {
233
+ const pct = toNumber(row[widget.valueKey]) / total;
234
+ const offset = segments.reduce((sum, segment) => sum + segment.dash, 0);
235
+ segments.push({
236
+ key: `${String(row[widget.labelKey])}-${index}`,
237
+ dash: pct * circumference,
238
+ offset,
239
+ color: colors[index % colors.length]
240
+ });
241
+ return segments;
242
+ }, []);
243
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.StatCard, {
244
+ title: widget.title,
245
+ headerExtra: widget.subtitle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
246
+ className: "nb-dashboard-widget__subtitle",
247
+ children: widget.subtitle
248
+ }) : void 0,
249
+ menuVisible: widget.menuVisible,
250
+ isLoading: loading,
251
+ className: "nb-dashboard-chart-card",
252
+ children: rows.length === 0 || total <= 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
253
+ className: "nb-dashboard-chart-empty",
254
+ children: "No data"
255
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
256
+ className: "nb-dashboard-donut",
257
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
258
+ className: "nb-dashboard-donut__chart",
259
+ role: "img",
260
+ "aria-label": widget.title,
261
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
262
+ viewBox: "0 0 100 100",
263
+ className: "nb-dashboard-donut__svg",
264
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
265
+ cx: "50",
266
+ cy: "50",
267
+ r: radius,
268
+ fill: "none",
269
+ stroke: "var(--surface-3)",
270
+ strokeWidth: stroke
271
+ }), arcs.map((arc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
272
+ cx: "50",
273
+ cy: "50",
274
+ r: radius,
275
+ fill: "none",
276
+ stroke: arc.color,
277
+ strokeWidth: stroke,
278
+ strokeDasharray: `${arc.dash} ${circumference - arc.dash}`,
279
+ strokeDashoffset: -arc.offset,
280
+ transform: "rotate(-90 50 50)",
281
+ strokeLinecap: "butt"
282
+ }, arc.key))]
283
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
284
+ className: "nb-dashboard-donut__center",
285
+ children: [widget.centerLabel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
286
+ className: "nb-dashboard-donut__center-label",
287
+ children: widget.centerLabel
288
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
289
+ className: "nb-dashboard-donut__center-value",
290
+ children: formatDashboardValue(centerValue, widget.valueFormat)
291
+ })]
292
+ })]
293
+ }), widget.showLegend !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
294
+ className: "nb-dashboard-donut__legend",
295
+ children: rows.map((row, index) => {
296
+ const label = String(row[widget.labelKey] ?? "");
297
+ const value = toNumber(row[widget.valueKey]);
298
+ const pct = total > 0 ? value / total * 100 : 0;
299
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
300
+ className: "nb-dashboard-donut__legend-item",
301
+ children: [
302
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
303
+ className: "nb-dashboard-donut__legend-swatch",
304
+ style: { background: colors[index % colors.length] }
305
+ }),
306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
307
+ className: "nb-dashboard-donut__legend-label",
308
+ children: label
309
+ }),
310
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
311
+ className: "nb-dashboard-donut__legend-value",
312
+ children: [pct.toFixed(0), "%"]
313
+ })
314
+ ]
315
+ }, `${label}-${index}`);
316
+ })
317
+ })]
318
+ })
319
+ });
320
+ }
321
+ //#endregion
322
+ //#region packages/dashboard/dashboard/widgets/StatWidgetView.tsx
323
+ function resolveTrend(widget, data) {
324
+ if (!widget.trend) return { value: null };
325
+ const raw = widget.trend.valuePath !== void 0 ? resolvePath(data, widget.trend.valuePath) : widget.trend.value;
326
+ const numeric = typeof raw === "number" ? raw : Number(raw);
327
+ return {
328
+ value: Number.isFinite(numeric) ? numeric : null,
329
+ label: widget.trend.label,
330
+ invert: widget.trend.invertColors
331
+ };
332
+ }
333
+ function StatWidgetView({ widget, data, loading }) {
334
+ const valueText = formatDashboardValue(widget.valuePath !== void 0 ? resolvePath(data, widget.valuePath) : widget.value, widget.format ?? "text");
335
+ const trend = resolveTrend(widget, data);
336
+ const trendDirection = trend.value === null || trend.value === 0 ? "flat" : trend.value > 0 ? "up" : "down";
337
+ const trendClass = trendDirection === "flat" ? "nb-stat-card__delta--flat" : trendDirection === "up" ? trend.invert ? "nb-stat-card__delta--down" : "nb-stat-card__delta--up" : trend.invert ? "nb-stat-card__delta--up" : "nb-stat-card__delta--down";
338
+ const trendArrow = trendDirection === "up" ? "▲" : trendDirection === "down" ? "▼" : "—";
339
+ const trendText = trend.value === null ? null : `${trendArrow} ${formatDashboardValue(Math.abs(trend.value), "percent")}${trend.label ? ` ${trend.label}` : ""}`;
340
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.StatCard, {
341
+ title: widget.title,
342
+ menuVisible: widget.menuVisible,
343
+ isLoading: loading,
344
+ className: `nb-dashboard-stat nb-dashboard-stat--${widget.iconTone ?? "accent"}`,
345
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
346
+ className: "nb-dashboard-stat__content",
347
+ children: [widget.icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
348
+ className: `nb-dashboard-stat__icon nb-dashboard-stat__icon--${widget.iconTone ?? "accent"}`,
349
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
350
+ className: `ph ${widget.icon}`,
351
+ "aria-hidden": "true"
352
+ })
353
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
354
+ className: "nb-dashboard-stat__metrics",
355
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
356
+ className: "nb-stat-card__value nb-stat-card__value--2xl",
357
+ children: valueText
358
+ }), trendText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
359
+ className: `nb-stat-card__delta ${trendClass}`,
360
+ children: trendText
361
+ })]
362
+ })]
363
+ })
364
+ });
365
+ }
366
+ //#endregion
367
+ //#region packages/dashboard/dashboard/widgets/TableWidgetView.tsx
368
+ function renderCell(row, column) {
369
+ const raw = row[column.key];
370
+ if (column.badge) {
371
+ const key = raw == null ? "" : String(raw);
372
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Badge, {
373
+ variant: column.badge[key] ?? "secondary",
374
+ size: "sm",
375
+ children: column.badgeLabels?.[key] ?? key.replace(/_/g, " ")
376
+ });
377
+ }
378
+ return formatDashboardValue(raw, column.format ?? "text");
379
+ }
380
+ function TableWidgetView({ widget, data, loading }) {
381
+ const rows = resolveArray(data, widget.dataPath);
382
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.StatCard, {
383
+ title: widget.title,
384
+ headerExtra: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
385
+ className: "nb-dashboard-table__header-extra",
386
+ children: [widget.subtitle && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
387
+ className: "nb-dashboard-widget__subtitle",
388
+ children: widget.subtitle
389
+ }), widget.viewAll && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Link, {
390
+ to: widget.viewAll.to,
391
+ className: "nb-dashboard-table__view-all",
392
+ children: widget.viewAll.label ?? "View all"
393
+ })]
394
+ }),
395
+ menuVisible: widget.menuVisible,
396
+ isLoading: loading,
397
+ className: "nb-dashboard-table-card",
398
+ children: rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.EmptyState, {
399
+ size: "sm",
400
+ icon: "ph ph-table",
401
+ title: widget.emptyTitle ?? "No records yet",
402
+ description: widget.emptyDescription
403
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
404
+ className: "nb-dashboard-table-wrap",
405
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
406
+ className: "nb-dashboard-table",
407
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: widget.columns.map((column) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
408
+ className: column.align ? `nb-dashboard-table__cell--${column.align}` : void 0,
409
+ children: column.label
410
+ }, column.key)) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: rows.map((row, rowIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: widget.columns.map((column) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
411
+ className: column.align ? `nb-dashboard-table__cell--${column.align}` : void 0,
412
+ children: renderCell(row, column)
413
+ }, column.key)) }, rowIndex)) })]
414
+ })
415
+ })
416
+ });
417
+ }
418
+ //#endregion
419
+ //#region packages/dashboard/dashboard/widgets/WidgetRenderer.tsx
420
+ function WidgetRenderer({ widget, data, loading }) {
421
+ switch (widget.type) {
422
+ case "stat": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatWidgetView, {
423
+ widget,
424
+ data,
425
+ loading
426
+ });
427
+ case "bar-chart": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BarChartWidgetView, {
428
+ widget,
429
+ data,
430
+ loading
431
+ });
432
+ case "donut-chart": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DonutChartWidgetView, {
433
+ widget,
434
+ data,
435
+ loading
436
+ });
437
+ case "table": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TableWidgetView, {
438
+ widget,
439
+ data,
440
+ loading
441
+ });
442
+ default: return null;
443
+ }
444
+ }
445
+ //#endregion
446
+ //#region packages/dashboard/dashboard/DashboardPage.tsx
447
+ function sectionClassName(section) {
448
+ const layout = section.layout ?? "grid";
449
+ if (layout === "grid") return (typeof section.columns === "number" ? `repeat(${section.columns}, minmax(0, 1fr))` : section.columns) ? "nb-dashboard-section nb-dashboard-section--grid" : "nb-dashboard-section nb-dashboard-section--grid";
450
+ return `nb-dashboard-section nb-dashboard-section--${layout}`;
451
+ }
452
+ function sectionStyle(section) {
453
+ if (section.layout !== "grid" && section.layout !== void 0) return void 0;
454
+ const columns = typeof section.columns === "number" ? `repeat(${section.columns}, minmax(0, 1fr))` : section.columns;
455
+ if (!columns) return void 0;
456
+ return { gridTemplateColumns: String(columns) };
457
+ }
458
+ function DashboardSectionView({ section, data, loading }) {
459
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
460
+ className: sectionClassName(section),
461
+ style: sectionStyle(section),
462
+ "data-section": section.id,
463
+ children: section.widgets.map((widget) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WidgetRenderer, {
464
+ widget,
465
+ data,
466
+ loading
467
+ }, widget.id))
468
+ });
469
+ }
470
+ function DashboardPage({ config }) {
471
+ const fetched = useDashboardData(config.dataUrl, config.refreshInterval);
472
+ const { data, loading, error, refetch } = config.useData?.() ?? fetched;
473
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
474
+ className: "view-wrapper-scroll nb-dashboard-page",
475
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_nubitio_ui.AppToolbar, {
476
+ title: config.title,
477
+ onRefresh: refetch,
478
+ children: [error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
479
+ className: "nb-dashboard-page__error",
480
+ children: error
481
+ }), config.sections.map((section, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DashboardSectionView, {
482
+ section,
483
+ data,
484
+ loading
485
+ }, section.id ?? `section-${index}`))]
486
+ }), loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
487
+ className: "nb-dashboard-page__loading",
488
+ "aria-hidden": "true",
489
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "nb-dashboard-page__spinner" })
490
+ })]
491
+ });
492
+ }
493
+ //#endregion
494
+ //#region packages/dashboard/dashboard/widgetBuilders.ts
495
+ function statWidget(config) {
496
+ return {
497
+ type: "stat",
498
+ menuVisible: false,
499
+ ...config
500
+ };
501
+ }
502
+ function barChartWidget(config) {
503
+ return {
504
+ type: "bar-chart",
505
+ menuVisible: false,
506
+ valueFormat: "currency",
507
+ height: 200,
508
+ ...config
509
+ };
510
+ }
511
+ function donutChartWidget(config) {
512
+ return {
513
+ type: "donut-chart",
514
+ menuVisible: false,
515
+ showLegend: true,
516
+ valueFormat: "currency",
517
+ ...config
518
+ };
519
+ }
520
+ function tableWidget(config) {
521
+ return {
522
+ type: "table",
523
+ menuVisible: false,
524
+ ...config
525
+ };
526
+ }
527
+ //#endregion
528
+ exports.DashboardPage = DashboardPage;
529
+ exports.barChartWidget = barChartWidget;
530
+ exports.defineDashboard = defineDashboard;
531
+ exports.donutChartWidget = donutChartWidget;
532
+ exports.formatDashboardValue = formatDashboardValue;
533
+ exports.resolveArray = resolveArray;
534
+ exports.resolvePath = resolvePath;
535
+ exports.statWidget = statWidget;
536
+ exports.tableWidget = tableWidget;
537
+ exports.useDashboardData = useDashboardData;