@burdenoff/microfe-bigconsole 2026.1004.4 → 2026.1004.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/dist/bigconsole/components/DrilldownDashboardRenderer.d.ts.map +1 -1
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js +57 -56
- package/dist/bigconsole/components/DrilldownDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/DrilldownModal.d.ts.map +1 -1
- package/dist/bigconsole/components/DrilldownModal.js +41 -40
- package/dist/bigconsole/components/DrilldownModal.js.map +1 -1
- package/dist/bigconsole/utils/drilldownParams.d.ts +3 -0
- package/dist/bigconsole/utils/drilldownParams.d.ts.map +1 -0
- package/dist/bigconsole/utils/drilldownParams.js +8 -0
- package/dist/bigconsole/utils/drilldownParams.js.map +1 -0
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DrilldownDashboardRenderer.d.ts","sourceRoot":"","sources":["../../../src/bigconsole/components/DrilldownDashboardRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA6B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"DrilldownDashboardRenderer.d.ts","sourceRoot":"","sources":["../../../src/bigconsole/components/DrilldownDashboardRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA6B,MAAM,OAAO,CAAC;AAc3D,MAAM,WAAW,+BAA+B;IAC9C,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AA8ED,eAAO,MAAM,0BAA0B,EAAE,EAAE,CAAC,+BAA+B,CAgQ1E,CAAC;AAmBF,eAAe,0BAA0B,CAAC"}
|
|
@@ -4,11 +4,12 @@ import "./common/index.js";
|
|
|
4
4
|
import { mapFromFederationWidgetType as i } from "../utils/widgetTypeMapping.js";
|
|
5
5
|
import { MODAL_TARGET_PAGE_PARAM as a } from "../hooks/useDrilldown.js";
|
|
6
6
|
import o from "./widgets/WidgetWrapper.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { getVisibleDrilldownParams as s } from "../utils/drilldownParams.js";
|
|
8
|
+
import { memo as c, useEffect as l, useState as u } from "react";
|
|
9
|
+
import { useApolloClient as d } from "@apollo/client/react";
|
|
10
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
10
11
|
//#region src/bigconsole/components/DrilldownDashboardRenderer.tsx
|
|
11
|
-
function
|
|
12
|
+
function m(e, t) {
|
|
12
13
|
let n = t * 4, r = {
|
|
13
14
|
x: 0,
|
|
14
15
|
y: n,
|
|
@@ -48,7 +49,7 @@ function p(e, t) {
|
|
|
48
49
|
updatedAt: e.updatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
function
|
|
52
|
+
function h(e, t) {
|
|
52
53
|
let n = {
|
|
53
54
|
x: 0,
|
|
54
55
|
y: t * 4,
|
|
@@ -64,20 +65,20 @@ function m(e, t) {
|
|
|
64
65
|
positionHeight: 4
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
|
-
var
|
|
68
|
-
let
|
|
69
|
-
if (
|
|
68
|
+
var g = c(function({ dashboardId: i, params: o, live: c = !1, refreshRevision: g = 0 }) {
|
|
69
|
+
let v = d(), [y, b] = u(!0), [x, S] = u(null), [C, w] = u(null), [T, E] = u([]);
|
|
70
|
+
if (l(() => {
|
|
70
71
|
if (!i) {
|
|
71
|
-
|
|
72
|
+
b(!1);
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
75
|
let n = !1, r = !1;
|
|
75
|
-
|
|
76
|
+
b(!0);
|
|
76
77
|
async function a() {
|
|
77
78
|
if (!r) {
|
|
78
|
-
r = !0,
|
|
79
|
+
r = !0, S(null);
|
|
79
80
|
try {
|
|
80
|
-
let r = await
|
|
81
|
+
let r = await v.query({
|
|
81
82
|
query: e,
|
|
82
83
|
variables: { id: i },
|
|
83
84
|
fetchPolicy: "network-only"
|
|
@@ -85,35 +86,35 @@ var h = s(function({ dashboardId: i, params: o, live: s = !1, refreshRevision: h
|
|
|
85
86
|
if (n) return;
|
|
86
87
|
let a = r.data?.getDashboard;
|
|
87
88
|
if (!a) {
|
|
88
|
-
|
|
89
|
+
S("Dashboard not found"), b(!1);
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
91
92
|
let o = a.pages?.edges?.map((e) => ({
|
|
92
93
|
id: e.node.id,
|
|
93
94
|
name: e.node.name || "Page 1"
|
|
94
95
|
})) || [];
|
|
95
|
-
|
|
96
|
+
w({
|
|
96
97
|
id: a.id,
|
|
97
98
|
name: a.name,
|
|
98
99
|
description: a.description,
|
|
99
100
|
pages: o
|
|
100
101
|
});
|
|
101
|
-
let s = await
|
|
102
|
+
let s = await v.query({
|
|
102
103
|
query: t,
|
|
103
104
|
variables: { dashboardId: i },
|
|
104
105
|
fetchPolicy: "network-only"
|
|
105
106
|
});
|
|
106
107
|
if (n) return;
|
|
107
|
-
|
|
108
|
+
E((s.data?.listWidgetsByDashboard || []).map((e, t) => m(e, t)));
|
|
108
109
|
} catch (e) {
|
|
109
|
-
n ||
|
|
110
|
+
n || S(e instanceof Error ? e.message : "Failed to load dashboard");
|
|
110
111
|
} finally {
|
|
111
|
-
r = !1, n ||
|
|
112
|
+
r = !1, n || b(!1);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
a();
|
|
116
|
-
let o =
|
|
117
|
+
let o = c ? window.setInterval(() => {
|
|
117
118
|
document.visibilityState === "visible" && a();
|
|
118
119
|
}, 4e3) : void 0;
|
|
119
120
|
return () => {
|
|
@@ -121,35 +122,35 @@ var h = s(function({ dashboardId: i, params: o, live: s = !1, refreshRevision: h
|
|
|
121
122
|
};
|
|
122
123
|
}, [
|
|
123
124
|
i,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
]),
|
|
125
|
+
v,
|
|
126
|
+
c,
|
|
127
|
+
g
|
|
128
|
+
]), y) return /* @__PURE__ */ f("div", {
|
|
128
129
|
className: "flex items-center justify-center h-full",
|
|
129
|
-
children: /* @__PURE__ */
|
|
130
|
+
children: /* @__PURE__ */ p("div", {
|
|
130
131
|
className: "flex flex-col items-center gap-3",
|
|
131
|
-
children: [/* @__PURE__ */
|
|
132
|
+
children: [/* @__PURE__ */ f("div", { className: "w-8 h-8 border-2 border-action-primary-bg border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ f("p", {
|
|
132
133
|
className: "text-sm text-text-secondary",
|
|
133
134
|
children: "Loading dashboard..."
|
|
134
135
|
})]
|
|
135
136
|
})
|
|
136
137
|
});
|
|
137
|
-
if (
|
|
138
|
+
if (x) return /* @__PURE__ */ f("div", {
|
|
138
139
|
className: "flex h-full items-center justify-center",
|
|
139
140
|
role: "alert",
|
|
140
|
-
children: /* @__PURE__ */
|
|
141
|
+
children: /* @__PURE__ */ f(n, {
|
|
141
142
|
headingLevel: 3,
|
|
142
143
|
tone: "danger",
|
|
143
144
|
className: "w-full max-w-md",
|
|
144
145
|
"data-testid": "drilldown-error",
|
|
145
146
|
title: "Could not load this dashboard",
|
|
146
|
-
description:
|
|
147
|
-
icon: /* @__PURE__ */
|
|
147
|
+
description: x,
|
|
148
|
+
icon: /* @__PURE__ */ f("svg", {
|
|
148
149
|
className: "h-icon-md w-icon-md",
|
|
149
150
|
fill: "none",
|
|
150
151
|
stroke: "currentColor",
|
|
151
152
|
viewBox: "0 0 24 24",
|
|
152
|
-
children: /* @__PURE__ */
|
|
153
|
+
children: /* @__PURE__ */ f("path", {
|
|
153
154
|
strokeLinecap: "round",
|
|
154
155
|
strokeLinejoin: "round",
|
|
155
156
|
strokeWidth: 1.5,
|
|
@@ -158,69 +159,69 @@ var h = s(function({ dashboardId: i, params: o, live: s = !1, refreshRevision: h
|
|
|
158
159
|
})
|
|
159
160
|
})
|
|
160
161
|
});
|
|
161
|
-
if (!
|
|
162
|
+
if (!C || T.length === 0) return /* @__PURE__ */ f("div", {
|
|
162
163
|
className: "flex items-center justify-center h-full",
|
|
163
|
-
children: /* @__PURE__ */
|
|
164
|
+
children: /* @__PURE__ */ p("div", {
|
|
164
165
|
className: "flex flex-col items-center gap-3 text-center",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
166
|
+
children: [/* @__PURE__ */ f("svg", {
|
|
166
167
|
className: "w-12 h-12 text-text-muted",
|
|
167
168
|
fill: "none",
|
|
168
169
|
stroke: "currentColor",
|
|
169
170
|
viewBox: "0 0 24 24",
|
|
170
|
-
children: /* @__PURE__ */
|
|
171
|
+
children: /* @__PURE__ */ f("path", {
|
|
171
172
|
strokeLinecap: "round",
|
|
172
173
|
strokeLinejoin: "round",
|
|
173
174
|
strokeWidth: 1.5,
|
|
174
175
|
d: "M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
|
|
175
176
|
})
|
|
176
|
-
}), /* @__PURE__ */
|
|
177
|
+
}), /* @__PURE__ */ f("p", {
|
|
177
178
|
className: "text-sm text-text-secondary",
|
|
178
|
-
children:
|
|
179
|
+
children: C ? "This dashboard has no widgets" : "Dashboard not found"
|
|
179
180
|
})]
|
|
180
181
|
})
|
|
181
182
|
});
|
|
182
|
-
let
|
|
183
|
-
return /* @__PURE__ */
|
|
183
|
+
let D = s(o), O = o[a], k = C.pages.find((e) => e.id === O) ?? C.pages[0] ?? null, A = (k ? T.filter((e) => !e.pageId || e.pageId === k.id) : T).map((e, t) => h(e, t));
|
|
184
|
+
return /* @__PURE__ */ p(r, {
|
|
184
185
|
className: "w-full",
|
|
185
186
|
children: [
|
|
186
|
-
/* @__PURE__ */
|
|
187
|
+
/* @__PURE__ */ f(n, {
|
|
187
188
|
emphasis: "bare",
|
|
188
189
|
padding: "none",
|
|
189
190
|
headingLevel: 3,
|
|
190
|
-
title:
|
|
191
|
-
description:
|
|
191
|
+
title: C.name,
|
|
192
|
+
description: C.pages.length > 1 && k ? /* @__PURE__ */ f("span", {
|
|
192
193
|
"data-testid": "drilldown-dashboard-page",
|
|
193
|
-
children:
|
|
194
|
-
}) :
|
|
194
|
+
children: k.name
|
|
195
|
+
}) : C.description,
|
|
195
196
|
"data-testid": "drilldown-dashboard-header"
|
|
196
197
|
}),
|
|
197
|
-
|
|
198
|
+
D.length > 0 && /* @__PURE__ */ f(n, {
|
|
198
199
|
emphasis: "muted",
|
|
199
200
|
padding: "compact",
|
|
200
201
|
headingLevel: 3,
|
|
201
202
|
title: "Filtered by",
|
|
202
203
|
"data-testid": "drilldown-params",
|
|
203
204
|
bodyClassName: "flex flex-wrap gap-control-gap",
|
|
204
|
-
children:
|
|
205
|
+
children: D.map(([e, t]) => /* @__PURE__ */ p("span", {
|
|
205
206
|
className: "inline-flex items-center rounded-pill bg-bg-surface px-2 py-1 text-xs text-text-secondary",
|
|
206
|
-
children: [/* @__PURE__ */
|
|
207
|
+
children: [/* @__PURE__ */ p("span", {
|
|
207
208
|
className: "font-medium text-text-primary",
|
|
208
209
|
children: [e, ":"]
|
|
209
|
-
}), /* @__PURE__ */
|
|
210
|
+
}), /* @__PURE__ */ f("span", {
|
|
210
211
|
className: "ml-1",
|
|
211
212
|
children: typeof t == "object" && t ? JSON.stringify(t) : String(t)
|
|
212
213
|
})]
|
|
213
214
|
}, e))
|
|
214
215
|
}),
|
|
215
|
-
/* @__PURE__ */
|
|
216
|
+
/* @__PURE__ */ p("div", {
|
|
216
217
|
className: "flex flex-col gap-form-gap",
|
|
217
|
-
children: [
|
|
218
|
+
children: [A.length === 0 && /* @__PURE__ */ f("p", {
|
|
218
219
|
className: "text-sm text-text-secondary",
|
|
219
220
|
"data-testid": "drilldown-page-empty",
|
|
220
221
|
children: "This page has no widgets"
|
|
221
|
-
}),
|
|
222
|
+
}), A.map((e) => {
|
|
222
223
|
let t = e.config?._internalWidgetType, n = e.type;
|
|
223
|
-
return /* @__PURE__ */
|
|
224
|
+
return /* @__PURE__ */ f("div", {
|
|
224
225
|
className: "min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-surface",
|
|
225
226
|
style: n === "LINE_CHART" || n === "chart" || t === "chart" || t === "funnel_chart" ? { height: "clamp(220px, 45vh, 350px)" } : n === "TABLE" || n === "table" || t === "table" || t === "list" || t === "pivot_table" ? {
|
|
226
227
|
minHeight: "min(250px, 40vh)",
|
|
@@ -229,7 +230,7 @@ var h = s(function({ dashboardId: i, params: o, live: s = !1, refreshRevision: h
|
|
|
229
230
|
minHeight: "min(120px, 25vh)",
|
|
230
231
|
maxHeight: "min(300px, 50vh)"
|
|
231
232
|
},
|
|
232
|
-
children: /* @__PURE__ */
|
|
233
|
+
children: /* @__PURE__ */ f(_, {
|
|
233
234
|
widget: e,
|
|
234
235
|
filterValues: o
|
|
235
236
|
})
|
|
@@ -238,10 +239,10 @@ var h = s(function({ dashboardId: i, params: o, live: s = !1, refreshRevision: h
|
|
|
238
239
|
})
|
|
239
240
|
]
|
|
240
241
|
});
|
|
241
|
-
}),
|
|
242
|
-
return /* @__PURE__ */
|
|
242
|
+
}), _ = c(function({ widget: e, filterValues: t }) {
|
|
243
|
+
return /* @__PURE__ */ f("div", {
|
|
243
244
|
className: "h-full [&_[data-widget-id]]:border-transparent [&_[data-widget-id]:hover]:border-transparent",
|
|
244
|
-
children: /* @__PURE__ */
|
|
245
|
+
children: /* @__PURE__ */ f(o, {
|
|
245
246
|
widget: e,
|
|
246
247
|
isLoading: !1,
|
|
247
248
|
error: null,
|
|
@@ -250,6 +251,6 @@ var h = s(function({ dashboardId: i, params: o, live: s = !1, refreshRevision: h
|
|
|
250
251
|
});
|
|
251
252
|
});
|
|
252
253
|
//#endregion
|
|
253
|
-
export {
|
|
254
|
+
export { g as default };
|
|
254
255
|
|
|
255
256
|
//# sourceMappingURL=DrilldownDashboardRenderer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DrilldownDashboardRenderer.js","names":[],"sources":["../../../src/bigconsole/components/DrilldownDashboardRenderer.tsx"],"sourcesContent":["/**\n * DrilldownDashboardRenderer Component\n *\n * Renders a dashboard inside the DrilldownModal.\n * Fetches dashboard data and displays the target page's widgets (the page named\n * by the drilldown, else the first page) with passed parameters (BOFF-7625).\n */\n\nimport { type FC, memo, useEffect, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport type { Widget } from '../types';\nimport { WidgetWrapper } from './widgets/WidgetWrapper';\nimport { Section, SectionStack } from './common';\nimport { ListWidgetsByDashboardDocument, GetDashboardDocument } from '../../generated/wspace-operations';\nimport { mapFromFederationWidgetType } from '../utils/widgetTypeMapping';\nimport { MODAL_TARGET_PAGE_PARAM } from '../hooks/useDrilldown';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DrilldownDashboardRendererProps {\n /** Target dashboard ID to render */\n dashboardId: string;\n live?: boolean;\n refreshRevision?: number;\n /** Parameters passed from drilldown (can be used for filtering) */\n params: Record<string, unknown>;\n}\n\ninterface DashboardData {\n id: string;\n name: string;\n description?: string;\n pages: Array<{\n id: string;\n name: string;\n }>;\n}\n\n// ============================================================================\n// Helper: Normalize widget from GraphQL\n// ============================================================================\n\nfunction normalizeWidget(raw: Record<string, unknown>, index: number): Widget {\n // For drilldown modal, stack widgets vertically with compact sizing\n const posX = 0;\n const posY = index * 4; // Stack vertically, 4 row units per widget\n const posW = 12; // Full width\n const posH = 4; // Better height for widget content\n\n const position = { x: posX, y: posY, width: posW, height: posH };\n\n // Get the config object\n const config = (raw.config as Record<string, unknown>) || {};\n\n // Map widget type correctly: check _internalWidgetType first, then map from federation type\n // This is critical — federation types like 'KPI' must be mapped to internal types like 'metric_card'\n const internalType = config._internalWidgetType as string | undefined;\n const widgetType = internalType || mapFromFederationWidgetType((raw.type as string) || 'KPI');\n\n return {\n id: (raw.id as string) ?? '',\n pageId: (raw.pageId as string) || '',\n dashboardId: raw.dashboardId as string | undefined,\n type: widgetType,\n title: (raw.title as string) || 'Untitled Widget',\n renderer: raw.renderer || 'BIGCONSOLE',\n template: raw.template || null,\n dataSinkId: raw.dataSinkId || null,\n datasetId: raw.datasetId || null,\n parserId: raw.parserId || null,\n parserRules: raw.parserRules || null,\n config: config,\n position: position,\n positionX: posX,\n positionY: posY,\n positionWidth: posW,\n positionHeight: posH,\n positionResponsive: raw.positionResponsive as Widget['positionResponsive'],\n rowOrder: (raw.rowOrder as number | null) ?? null,\n refreshInterval: raw.refreshInterval,\n lastRefreshedAt: raw.lastRefreshedAt,\n cacheTTL: (raw.cacheTTL as number | null) ?? null,\n drilldown: raw.drilldown as Widget['drilldown'],\n actions: (raw.actions as Widget['actions']) || [],\n translations: (raw.translations as Widget['translations']) || [],\n metadata: raw.metadata as Record<string, unknown> | undefined,\n context: raw.context,\n // Include widget data from backend (critical for showing real data in modal)\n data: raw.data || null,\n createdAt: (raw.createdAt as string) || new Date().toISOString(),\n updatedAt: (raw.updatedAt as string) || new Date().toISOString(),\n } as Widget;\n}\n\n/** Place a widget at a given slot of the modal's single-column stack. */\nfunction normalizeStacking(widget: Widget, index: number): Widget {\n const position = { x: 0, y: index * 4, width: 12, height: 4 };\n return { ...widget, position, positionX: 0, positionY: position.y, positionWidth: 12, positionHeight: 4 };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DrilldownDashboardRenderer: FC<DrilldownDashboardRendererProps> = memo(\n function DrilldownDashboardRenderer({ dashboardId, params, live = false, refreshRevision = 0 }) {\n const apolloClient = useApolloClient();\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [dashboard, setDashboard] = useState<DashboardData | null>(null);\n const [widgets, setWidgets] = useState<Widget[]>([]);\n\n // Fetch dashboard and widgets — only when dashboardId is provided\n useEffect(() => {\n if (!dashboardId) {\n setLoading(false);\n return;\n }\n\n let cancelled = false;\n let fetching = false;\n setLoading(true);\n async function fetchData() {\n if (fetching) return;\n fetching = true;\n setError(null);\n\n try {\n // Fetch dashboard info\n interface DashboardQueryResult {\n getDashboard: {\n id: string;\n name: string;\n description?: string;\n pages?: {\n edges?: Array<{ node: { id: string; name?: string } }>;\n };\n } | null;\n }\n\n const dashboardResult = await apolloClient.query<DashboardQueryResult>({\n query: GetDashboardDocument,\n variables: { id: dashboardId },\n fetchPolicy: 'network-only',\n });\n\n if (cancelled) return;\n const dashboardData = dashboardResult.data?.getDashboard;\n if (!dashboardData) {\n setError('Dashboard not found');\n setLoading(false);\n return;\n }\n\n // Normalize dashboard\n const pages =\n dashboardData.pages?.edges?.map((edge) => ({\n id: edge.node.id,\n name: edge.node.name || 'Page 1',\n })) || [];\n\n setDashboard({\n id: dashboardData.id,\n name: dashboardData.name,\n description: dashboardData.description,\n pages,\n });\n\n // Fetch widgets for the dashboard\n const widgetsResult = await apolloClient.query<{ listWidgetsByDashboard: Record<string, unknown>[] }>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n\n if (cancelled) return;\n const widgetsList = widgetsResult.data?.listWidgetsByDashboard || [];\n setWidgets(widgetsList.map((w, i) => normalizeWidget(w, i)));\n } catch (err) {\n if (!cancelled) setError(err instanceof Error ? err.message : 'Failed to load dashboard');\n } finally {\n fetching = false;\n if (!cancelled) setLoading(false);\n }\n }\n\n void fetchData();\n const interval = live\n ? window.setInterval(() => {\n if (document.visibilityState === 'visible') void fetchData();\n }, 4000)\n : undefined;\n return () => {\n cancelled = true;\n if (interval !== undefined) window.clearInterval(interval);\n };\n }, [dashboardId, apolloClient, live, refreshRevision]);\n\n // Loading state\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"flex flex-col items-center gap-3\">\n <div className=\"w-8 h-8 border-2 border-action-primary-bg border-t-transparent rounded-full animate-spin\" />\n <p className=\"text-sm text-text-secondary\">Loading dashboard...</p>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex h-full items-center justify-center\" role=\"alert\">\n <Section\n headingLevel={3}\n tone=\"danger\"\n className=\"w-full max-w-md\"\n data-testid=\"drilldown-error\"\n title=\"Could not load this dashboard\"\n description={error}\n icon={\n <svg className=\"h-icon-md w-icon-md\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n }\n />\n </div>\n );\n }\n\n // Empty state\n if (!dashboard || widgets.length === 0) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"flex flex-col items-center gap-3 text-center\">\n <svg className=\"w-12 h-12 text-text-muted\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n <p className=\"text-sm text-text-secondary\">\n {dashboard ? 'This dashboard has no widgets' : 'Dashboard not found'}\n </p>\n </div>\n </div>\n );\n }\n\n const visibleParams = Object.entries(params).filter(([key]) => !key.startsWith('__'));\n\n // Show one page: the drilldown's target page when it still exists, else the\n // first page. Widgets without a page id (older payloads) are always shown.\n const requestedPageId = params[MODAL_TARGET_PAGE_PARAM];\n const activePage = dashboard.pages.find((page) => page.id === requestedPageId) ?? dashboard.pages[0] ?? null;\n const pageWidgets = activePage\n ? widgets.filter((widget) => !widget.pageId || widget.pageId === activePage.id)\n : widgets;\n // Re-flow the page's widgets top to bottom (the stacking was computed over\n // the whole dashboard).\n const stackedWidgets = pageWidgets.map((widget, index) => normalizeStacking(widget, index));\n\n return (\n <SectionStack className=\"w-full\">\n {/* Which board you drilled into — identity, not content. */}\n <Section\n emphasis=\"bare\"\n padding=\"none\"\n headingLevel={3}\n title={dashboard.name}\n description={\n dashboard.pages.length > 1 && activePage ? (\n <span data-testid=\"drilldown-dashboard-page\">{activePage.name}</span>\n ) : (\n dashboard.description\n )\n }\n data-testid=\"drilldown-dashboard-header\"\n />\n\n {/* The filter context you arrived with: supporting detail, recessed. */}\n {visibleParams.length > 0 && (\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={3}\n title=\"Filtered by\"\n data-testid=\"drilldown-params\"\n bodyClassName=\"flex flex-wrap gap-control-gap\"\n >\n {visibleParams.map(([key, value]) => (\n <span\n key={key}\n className=\"inline-flex items-center rounded-pill bg-bg-surface px-2 py-1 text-xs text-text-secondary\"\n >\n <span className=\"font-medium text-text-primary\">{key}:</span>\n <span className=\"ml-1\">\n {typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value)}\n </span>\n </span>\n ))}\n </Section>\n )}\n\n {/* Widgets List - simple vertical layout for modal */}\n <div className=\"flex flex-col gap-form-gap\">\n {stackedWidgets.length === 0 && (\n <p className=\"text-sm text-text-secondary\" data-testid=\"drilldown-page-empty\">\n This page has no widgets\n </p>\n )}\n {stackedWidgets.map((widget) => {\n // Chart widgets (LINE_CHART / chart) need explicit pixel height because\n // Recharts ResponsiveContainer height=\"100%\" requires a parent with\n // a definite height — minHeight alone doesn't work.\n const internalType = (widget.config as Record<string, unknown>)?._internalWidgetType as string | undefined;\n const widgetType = widget.type as string;\n const isChartWidget =\n widgetType === 'LINE_CHART' ||\n widgetType === 'chart' ||\n internalType === 'chart' ||\n internalType === 'funnel_chart';\n const isTableWidget =\n widgetType === 'TABLE' ||\n widgetType === 'table' ||\n internalType === 'table' ||\n internalType === 'list' ||\n internalType === 'pivot_table';\n\n // Charts need an explicit height (Recharts' ResponsiveContainer needs a\n // definite parent). Clamping against the viewport keeps a 350px chart\n // plus a 500px table from filling a phone screen twice over.\n const heightStyle = isChartWidget\n ? { height: 'clamp(220px, 45vh, 350px)' }\n : isTableWidget\n ? { minHeight: 'min(250px, 40vh)', maxHeight: 'min(500px, 65vh)' }\n : { minHeight: 'min(120px, 25vh)', maxHeight: 'min(300px, 50vh)' };\n\n return (\n <div\n key={widget.id}\n className=\"min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-surface\"\n style={heightStyle}\n >\n <WidgetInModal widget={widget} filterValues={params} />\n </div>\n );\n })}\n </div>\n </SectionStack>\n );\n }\n);\n\n// ============================================================================\n// Widget In Modal Component\n// ============================================================================\n\ninterface WidgetInModalProps {\n widget: Widget;\n filterValues?: Record<string, unknown>;\n}\n\nconst WidgetInModal: FC<WidgetInModalProps> = memo(function WidgetInModal({ widget, filterValues }) {\n return (\n <div className=\"h-full [&_[data-widget-id]]:border-transparent [&_[data-widget-id]:hover]:border-transparent\">\n <WidgetWrapper widget={widget} isLoading={false} error={null} filterValues={filterValues} />\n </div>\n );\n});\n\nexport default DrilldownDashboardRenderer;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DrilldownDashboardRenderer.js","names":[],"sources":["../../../src/bigconsole/components/DrilldownDashboardRenderer.tsx"],"sourcesContent":["/**\n * DrilldownDashboardRenderer Component\n *\n * Renders a dashboard inside the DrilldownModal.\n * Fetches dashboard data and displays the target page's widgets (the page named\n * by the drilldown, else the first page) with passed parameters (BOFF-7625).\n */\n\nimport { type FC, memo, useEffect, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport type { Widget } from '../types';\nimport { WidgetWrapper } from './widgets/WidgetWrapper';\nimport { Section, SectionStack } from './common';\nimport { ListWidgetsByDashboardDocument, GetDashboardDocument } from '../../generated/wspace-operations';\nimport { mapFromFederationWidgetType } from '../utils/widgetTypeMapping';\nimport { MODAL_TARGET_PAGE_PARAM } from '../hooks/useDrilldown';\nimport { getVisibleDrilldownParams } from '../utils/drilldownParams';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DrilldownDashboardRendererProps {\n /** Target dashboard ID to render */\n dashboardId: string;\n live?: boolean;\n refreshRevision?: number;\n /** Parameters passed from drilldown (can be used for filtering) */\n params: Record<string, unknown>;\n}\n\ninterface DashboardData {\n id: string;\n name: string;\n description?: string;\n pages: Array<{\n id: string;\n name: string;\n }>;\n}\n\n// ============================================================================\n// Helper: Normalize widget from GraphQL\n// ============================================================================\n\nfunction normalizeWidget(raw: Record<string, unknown>, index: number): Widget {\n // For drilldown modal, stack widgets vertically with compact sizing\n const posX = 0;\n const posY = index * 4; // Stack vertically, 4 row units per widget\n const posW = 12; // Full width\n const posH = 4; // Better height for widget content\n\n const position = { x: posX, y: posY, width: posW, height: posH };\n\n // Get the config object\n const config = (raw.config as Record<string, unknown>) || {};\n\n // Map widget type correctly: check _internalWidgetType first, then map from federation type\n // This is critical — federation types like 'KPI' must be mapped to internal types like 'metric_card'\n const internalType = config._internalWidgetType as string | undefined;\n const widgetType = internalType || mapFromFederationWidgetType((raw.type as string) || 'KPI');\n\n return {\n id: (raw.id as string) ?? '',\n pageId: (raw.pageId as string) || '',\n dashboardId: raw.dashboardId as string | undefined,\n type: widgetType,\n title: (raw.title as string) || 'Untitled Widget',\n renderer: raw.renderer || 'BIGCONSOLE',\n template: raw.template || null,\n dataSinkId: raw.dataSinkId || null,\n datasetId: raw.datasetId || null,\n parserId: raw.parserId || null,\n parserRules: raw.parserRules || null,\n config: config,\n position: position,\n positionX: posX,\n positionY: posY,\n positionWidth: posW,\n positionHeight: posH,\n positionResponsive: raw.positionResponsive as Widget['positionResponsive'],\n rowOrder: (raw.rowOrder as number | null) ?? null,\n refreshInterval: raw.refreshInterval,\n lastRefreshedAt: raw.lastRefreshedAt,\n cacheTTL: (raw.cacheTTL as number | null) ?? null,\n drilldown: raw.drilldown as Widget['drilldown'],\n actions: (raw.actions as Widget['actions']) || [],\n translations: (raw.translations as Widget['translations']) || [],\n metadata: raw.metadata as Record<string, unknown> | undefined,\n context: raw.context,\n // Include widget data from backend (critical for showing real data in modal)\n data: raw.data || null,\n createdAt: (raw.createdAt as string) || new Date().toISOString(),\n updatedAt: (raw.updatedAt as string) || new Date().toISOString(),\n } as Widget;\n}\n\n/** Place a widget at a given slot of the modal's single-column stack. */\nfunction normalizeStacking(widget: Widget, index: number): Widget {\n const position = { x: 0, y: index * 4, width: 12, height: 4 };\n return { ...widget, position, positionX: 0, positionY: position.y, positionWidth: 12, positionHeight: 4 };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DrilldownDashboardRenderer: FC<DrilldownDashboardRendererProps> = memo(\n function DrilldownDashboardRenderer({ dashboardId, params, live = false, refreshRevision = 0 }) {\n const apolloClient = useApolloClient();\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [dashboard, setDashboard] = useState<DashboardData | null>(null);\n const [widgets, setWidgets] = useState<Widget[]>([]);\n\n // Fetch dashboard and widgets — only when dashboardId is provided\n useEffect(() => {\n if (!dashboardId) {\n setLoading(false);\n return;\n }\n\n let cancelled = false;\n let fetching = false;\n setLoading(true);\n async function fetchData() {\n if (fetching) return;\n fetching = true;\n setError(null);\n\n try {\n // Fetch dashboard info\n interface DashboardQueryResult {\n getDashboard: {\n id: string;\n name: string;\n description?: string;\n pages?: {\n edges?: Array<{ node: { id: string; name?: string } }>;\n };\n } | null;\n }\n\n const dashboardResult = await apolloClient.query<DashboardQueryResult>({\n query: GetDashboardDocument,\n variables: { id: dashboardId },\n fetchPolicy: 'network-only',\n });\n\n if (cancelled) return;\n const dashboardData = dashboardResult.data?.getDashboard;\n if (!dashboardData) {\n setError('Dashboard not found');\n setLoading(false);\n return;\n }\n\n // Normalize dashboard\n const pages =\n dashboardData.pages?.edges?.map((edge) => ({\n id: edge.node.id,\n name: edge.node.name || 'Page 1',\n })) || [];\n\n setDashboard({\n id: dashboardData.id,\n name: dashboardData.name,\n description: dashboardData.description,\n pages,\n });\n\n // Fetch widgets for the dashboard\n const widgetsResult = await apolloClient.query<{ listWidgetsByDashboard: Record<string, unknown>[] }>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n\n if (cancelled) return;\n const widgetsList = widgetsResult.data?.listWidgetsByDashboard || [];\n setWidgets(widgetsList.map((w, i) => normalizeWidget(w, i)));\n } catch (err) {\n if (!cancelled) setError(err instanceof Error ? err.message : 'Failed to load dashboard');\n } finally {\n fetching = false;\n if (!cancelled) setLoading(false);\n }\n }\n\n void fetchData();\n const interval = live\n ? window.setInterval(() => {\n if (document.visibilityState === 'visible') void fetchData();\n }, 4000)\n : undefined;\n return () => {\n cancelled = true;\n if (interval !== undefined) window.clearInterval(interval);\n };\n }, [dashboardId, apolloClient, live, refreshRevision]);\n\n // Loading state\n if (loading) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"flex flex-col items-center gap-3\">\n <div className=\"w-8 h-8 border-2 border-action-primary-bg border-t-transparent rounded-full animate-spin\" />\n <p className=\"text-sm text-text-secondary\">Loading dashboard...</p>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex h-full items-center justify-center\" role=\"alert\">\n <Section\n headingLevel={3}\n tone=\"danger\"\n className=\"w-full max-w-md\"\n data-testid=\"drilldown-error\"\n title=\"Could not load this dashboard\"\n description={error}\n icon={\n <svg className=\"h-icon-md w-icon-md\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n }\n />\n </div>\n );\n }\n\n // Empty state\n if (!dashboard || widgets.length === 0) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"flex flex-col items-center gap-3 text-center\">\n <svg className=\"w-12 h-12 text-text-muted\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n <p className=\"text-sm text-text-secondary\">\n {dashboard ? 'This dashboard has no widgets' : 'Dashboard not found'}\n </p>\n </div>\n </div>\n );\n }\n\n const visibleParams = getVisibleDrilldownParams(params);\n\n // Show one page: the drilldown's target page when it still exists, else the\n // first page. Widgets without a page id (older payloads) are always shown.\n const requestedPageId = params[MODAL_TARGET_PAGE_PARAM];\n const activePage = dashboard.pages.find((page) => page.id === requestedPageId) ?? dashboard.pages[0] ?? null;\n const pageWidgets = activePage\n ? widgets.filter((widget) => !widget.pageId || widget.pageId === activePage.id)\n : widgets;\n // Re-flow the page's widgets top to bottom (the stacking was computed over\n // the whole dashboard).\n const stackedWidgets = pageWidgets.map((widget, index) => normalizeStacking(widget, index));\n\n return (\n <SectionStack className=\"w-full\">\n {/* Which board you drilled into — identity, not content. */}\n <Section\n emphasis=\"bare\"\n padding=\"none\"\n headingLevel={3}\n title={dashboard.name}\n description={\n dashboard.pages.length > 1 && activePage ? (\n <span data-testid=\"drilldown-dashboard-page\">{activePage.name}</span>\n ) : (\n dashboard.description\n )\n }\n data-testid=\"drilldown-dashboard-header\"\n />\n\n {/* The filter context you arrived with: supporting detail, recessed. */}\n {visibleParams.length > 0 && (\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={3}\n title=\"Filtered by\"\n data-testid=\"drilldown-params\"\n bodyClassName=\"flex flex-wrap gap-control-gap\"\n >\n {visibleParams.map(([key, value]) => (\n <span\n key={key}\n className=\"inline-flex items-center rounded-pill bg-bg-surface px-2 py-1 text-xs text-text-secondary\"\n >\n <span className=\"font-medium text-text-primary\">{key}:</span>\n <span className=\"ml-1\">\n {typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value)}\n </span>\n </span>\n ))}\n </Section>\n )}\n\n {/* Widgets List - simple vertical layout for modal */}\n <div className=\"flex flex-col gap-form-gap\">\n {stackedWidgets.length === 0 && (\n <p className=\"text-sm text-text-secondary\" data-testid=\"drilldown-page-empty\">\n This page has no widgets\n </p>\n )}\n {stackedWidgets.map((widget) => {\n // Chart widgets (LINE_CHART / chart) need explicit pixel height because\n // Recharts ResponsiveContainer height=\"100%\" requires a parent with\n // a definite height — minHeight alone doesn't work.\n const internalType = (widget.config as Record<string, unknown>)?._internalWidgetType as string | undefined;\n const widgetType = widget.type as string;\n const isChartWidget =\n widgetType === 'LINE_CHART' ||\n widgetType === 'chart' ||\n internalType === 'chart' ||\n internalType === 'funnel_chart';\n const isTableWidget =\n widgetType === 'TABLE' ||\n widgetType === 'table' ||\n internalType === 'table' ||\n internalType === 'list' ||\n internalType === 'pivot_table';\n\n // Charts need an explicit height (Recharts' ResponsiveContainer needs a\n // definite parent). Clamping against the viewport keeps a 350px chart\n // plus a 500px table from filling a phone screen twice over.\n const heightStyle = isChartWidget\n ? { height: 'clamp(220px, 45vh, 350px)' }\n : isTableWidget\n ? { minHeight: 'min(250px, 40vh)', maxHeight: 'min(500px, 65vh)' }\n : { minHeight: 'min(120px, 25vh)', maxHeight: 'min(300px, 50vh)' };\n\n return (\n <div\n key={widget.id}\n className=\"min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-surface\"\n style={heightStyle}\n >\n <WidgetInModal widget={widget} filterValues={params} />\n </div>\n );\n })}\n </div>\n </SectionStack>\n );\n }\n);\n\n// ============================================================================\n// Widget In Modal Component\n// ============================================================================\n\ninterface WidgetInModalProps {\n widget: Widget;\n filterValues?: Record<string, unknown>;\n}\n\nconst WidgetInModal: FC<WidgetInModalProps> = memo(function WidgetInModal({ widget, filterValues }) {\n return (\n <div className=\"h-full [&_[data-widget-id]]:border-transparent [&_[data-widget-id]:hover]:border-transparent\">\n <WidgetWrapper widget={widget} isLoading={false} error={null} filterValues={filterValues} />\n </div>\n );\n});\n\nexport default DrilldownDashboardRenderer;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DrilldownModal.d.ts","sourceRoot":"","sources":["../../../src/bigconsole/components/DrilldownModal.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA6B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"DrilldownModal.d.ts","sourceRoot":"","sources":["../../../src/bigconsole/components/DrilldownModal.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA6B,MAAM,OAAO,CAAC;AAe3D,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5F,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAMD,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAoHjD,CAAC;AAYH,eAAe,cAAc,CAAC"}
|
|
@@ -1,54 +1,55 @@
|
|
|
1
1
|
import e from "../store/drilldownStore.js";
|
|
2
2
|
import { SECTION_FOCUS_RING as t } from "./common/sectionStyles.js";
|
|
3
3
|
import "./common/index.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { getVisibleDrilldownParams as n } from "../utils/drilldownParams.js";
|
|
5
|
+
import { memo as r, useCallback as i, useRef as a } from "react";
|
|
6
|
+
import { ResponsiveDialog as o, ResponsiveDialogContent as s, ResponsiveDialogDescription as c, ResponsiveDialogTitle as l } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
7
8
|
//#region src/bigconsole/components/DrilldownModal.tsx
|
|
8
|
-
var
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
}, [
|
|
12
|
-
|
|
13
|
-
}, [
|
|
14
|
-
return !
|
|
15
|
-
open:
|
|
9
|
+
var f = r(function({ renderDashboard: r, onClose: f }) {
|
|
10
|
+
let m = a(null), h = e((e) => e.isOpen), g = e((e) => e.dashboardId), _ = e((e) => e.params), v = e((e) => e.history), y = e((e) => e.closeModal), b = e((e) => e.popDrilldown), x = i(() => {
|
|
11
|
+
y(), f?.();
|
|
12
|
+
}, [y, f]), S = i(() => {
|
|
13
|
+
b();
|
|
14
|
+
}, [b]);
|
|
15
|
+
return !h || !g ? null : /* @__PURE__ */ u(o, {
|
|
16
|
+
open: h,
|
|
16
17
|
onOpenChange: (e) => {
|
|
17
|
-
e ||
|
|
18
|
+
e || x();
|
|
18
19
|
},
|
|
19
|
-
children: /* @__PURE__ */
|
|
20
|
+
children: /* @__PURE__ */ d(s, {
|
|
20
21
|
showCloseButton: !1,
|
|
21
22
|
onOpenAutoFocus: () => {
|
|
22
23
|
let e = document.activeElement;
|
|
23
|
-
e instanceof HTMLElement && e !== document.body && !e.closest("[role=\"dialog\"]") && (
|
|
24
|
+
e instanceof HTMLElement && e !== document.body && !e.closest("[role=\"dialog\"]") && (m.current = e);
|
|
24
25
|
},
|
|
25
26
|
onCloseAutoFocus: (e) => {
|
|
26
|
-
e.preventDefault(),
|
|
27
|
+
e.preventDefault(), m.current?.isConnected && m.current.focus();
|
|
27
28
|
},
|
|
28
29
|
className: "flex w-full md:w-[calc(100%-2rem)] max-w-5xl flex-col gap-0 overflow-hidden rounded-card bg-bg-surface p-0 sm:max-w-5xl",
|
|
29
30
|
style: { maxHeight: "calc(100dvh - 2rem)" },
|
|
30
31
|
children: [
|
|
31
|
-
/* @__PURE__ */
|
|
32
|
+
/* @__PURE__ */ u(c, {
|
|
32
33
|
className: "sr-only",
|
|
33
34
|
children: "Explore the linked dashboard and return to the previous drilldown level."
|
|
34
35
|
}),
|
|
35
|
-
/* @__PURE__ */
|
|
36
|
+
/* @__PURE__ */ d("div", {
|
|
36
37
|
className: "flex flex-shrink-0 flex-wrap items-center justify-between gap-control-gap border-b border-[var(--color-border-subtle)] px-4 py-3",
|
|
37
|
-
children: [/* @__PURE__ */
|
|
38
|
+
children: [/* @__PURE__ */ d("div", {
|
|
38
39
|
className: "flex min-w-0 items-center gap-3",
|
|
39
40
|
children: [
|
|
40
|
-
|
|
41
|
+
v.length > 0 && /* @__PURE__ */ u("button", {
|
|
41
42
|
type: "button",
|
|
42
|
-
onClick:
|
|
43
|
+
onClick: S,
|
|
43
44
|
className: "flex h-control-md w-[var(--size-controlHeight-md)] shrink-0 items-center justify-center rounded-button motion-safe:transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-bg-surface",
|
|
44
45
|
title: "Go back",
|
|
45
46
|
"aria-label": "Go back",
|
|
46
|
-
children: /* @__PURE__ */
|
|
47
|
+
children: /* @__PURE__ */ u("svg", {
|
|
47
48
|
className: "w-5 h-5 text-text-secondary",
|
|
48
49
|
fill: "none",
|
|
49
50
|
stroke: "currentColor",
|
|
50
51
|
viewBox: "0 0 24 24",
|
|
51
|
-
children: /* @__PURE__ */
|
|
52
|
+
children: /* @__PURE__ */ u("path", {
|
|
52
53
|
strokeLinecap: "round",
|
|
53
54
|
strokeLinejoin: "round",
|
|
54
55
|
strokeWidth: 2,
|
|
@@ -56,33 +57,33 @@ var d = n(function({ renderDashboard: n, onClose: d }) {
|
|
|
56
57
|
})
|
|
57
58
|
})
|
|
58
59
|
}),
|
|
59
|
-
/* @__PURE__ */ l
|
|
60
|
+
/* @__PURE__ */ u(l, {
|
|
60
61
|
className: "truncate text-sm font-semibold text-text-primary",
|
|
61
62
|
children: "Drilldown View"
|
|
62
63
|
}),
|
|
63
|
-
|
|
64
|
+
v.length > 0 && /* @__PURE__ */ d("span", {
|
|
64
65
|
className: "text-xs text-text-muted",
|
|
65
66
|
children: [
|
|
66
67
|
"(",
|
|
67
|
-
|
|
68
|
+
v.length + 1,
|
|
68
69
|
" level",
|
|
69
|
-
|
|
70
|
+
v.length > 0 ? "s" : "",
|
|
70
71
|
" deep)"
|
|
71
72
|
]
|
|
72
73
|
})
|
|
73
74
|
]
|
|
74
|
-
}), /* @__PURE__ */
|
|
75
|
+
}), /* @__PURE__ */ u("button", {
|
|
75
76
|
type: "button",
|
|
76
|
-
onClick:
|
|
77
|
+
onClick: x,
|
|
77
78
|
className: `flex h-control-md w-[var(--size-controlHeight-md)] shrink-0 items-center justify-center rounded-button motion-safe:transition-colors hover:bg-bg-sunken ${t}`,
|
|
78
79
|
title: "Close (Esc)",
|
|
79
80
|
"aria-label": "Close drilldown",
|
|
80
|
-
children: /* @__PURE__ */
|
|
81
|
+
children: /* @__PURE__ */ u("svg", {
|
|
81
82
|
className: "w-5 h-5 text-text-secondary",
|
|
82
83
|
fill: "none",
|
|
83
84
|
stroke: "currentColor",
|
|
84
85
|
viewBox: "0 0 24 24",
|
|
85
|
-
children: /* @__PURE__ */
|
|
86
|
+
children: /* @__PURE__ */ u("path", {
|
|
86
87
|
strokeLinecap: "round",
|
|
87
88
|
strokeLinejoin: "round",
|
|
88
89
|
strokeWidth: 2,
|
|
@@ -91,32 +92,32 @@ var d = n(function({ renderDashboard: n, onClose: d }) {
|
|
|
91
92
|
})
|
|
92
93
|
})]
|
|
93
94
|
}),
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
+
/* @__PURE__ */ u("div", {
|
|
95
96
|
className: "min-h-0 flex-1 overflow-y-auto bg-bg-sunken p-4",
|
|
96
|
-
children:
|
|
97
|
+
children: r ? r(g, _) : /* @__PURE__ */ u(p, {})
|
|
97
98
|
}),
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
+
/* @__PURE__ */ u("div", {
|
|
99
100
|
className: "flex-shrink-0 border-t border-[var(--color-border-subtle)] px-4 py-2",
|
|
100
|
-
children: /* @__PURE__ */
|
|
101
|
+
children: /* @__PURE__ */ d("div", {
|
|
101
102
|
className: "flex flex-wrap items-center justify-between gap-x-4 gap-y-1 text-xs text-text-muted",
|
|
102
|
-
children: [/* @__PURE__ */
|
|
103
|
+
children: [/* @__PURE__ */ d("span", { children: [
|
|
103
104
|
"Parameters:",
|
|
104
105
|
" ",
|
|
105
|
-
|
|
106
|
-
] }), /* @__PURE__ */
|
|
106
|
+
n(_).map(([e]) => e).join(", ") || "None"
|
|
107
|
+
] }), /* @__PURE__ */ u("span", { children: "Press Esc to close" })]
|
|
107
108
|
})
|
|
108
109
|
})
|
|
109
110
|
]
|
|
110
111
|
})
|
|
111
112
|
});
|
|
112
113
|
});
|
|
113
|
-
function
|
|
114
|
-
return /* @__PURE__ */
|
|
114
|
+
function p() {
|
|
115
|
+
return /* @__PURE__ */ u("div", {
|
|
115
116
|
className: "flex items-center justify-center h-full text-sm text-text-secondary",
|
|
116
117
|
children: "Unable to load drilldown content. Please try again."
|
|
117
118
|
});
|
|
118
119
|
}
|
|
119
120
|
//#endregion
|
|
120
|
-
export {
|
|
121
|
+
export { f as default };
|
|
121
122
|
|
|
122
123
|
//# sourceMappingURL=DrilldownModal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DrilldownModal.js","names":[],"sources":["../../../src/bigconsole/components/DrilldownModal.tsx"],"sourcesContent":["/**\n * DrilldownModal Component\n *\n * Modal overlay for displaying drilldown dashboards.\n * Supports nested drilldowns with back navigation.\n */\n\nimport { type FC, useCallback, useRef, memo } from 'react';\nimport { useDrilldownStore } from '../store/drilldownStore';\nimport { SECTION_FOCUS_RING } from './common';\nimport {\n ResponsiveDialog as Dialog,\n ResponsiveDialogContent as DialogContent,\n ResponsiveDialogTitle as DialogTitle,\n ResponsiveDialogDescription as DialogDescription,\n} from '@burdenoff/fe-libs/ui';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DrilldownModalProps {\n /** Custom content renderer for the dashboard */\n renderDashboard?: (dashboardId: string, params: Record<string, unknown>) => React.ReactNode;\n /** Called when modal closes */\n onClose?: () => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DrilldownModal: FC<DrilldownModalProps> = memo(function DrilldownModal({ renderDashboard, onClose }) {\n const returnFocus = useRef<HTMLElement | null>(null);\n const isOpen = useDrilldownStore((state) => state.isOpen);\n const dashboardId = useDrilldownStore((state) => state.dashboardId);\n const params = useDrilldownStore((state) => state.params);\n const history = useDrilldownStore((state) => state.history);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n const popDrilldown = useDrilldownStore((state) => state.popDrilldown);\n\n // Define handlers before effects that use them\n const handleClose = useCallback(() => {\n closeModal();\n onClose?.();\n }, [closeModal, onClose]);\n\n const handleBack = useCallback(() => {\n popDrilldown();\n }, [popDrilldown]);\n\n if (!isOpen || !dashboardId) {\n return null;\n }\n\n return (\n <Dialog\n open={isOpen}\n onOpenChange={(open) => {\n if (!open) handleClose();\n }}\n >\n <DialogContent\n showCloseButton={false}\n onOpenAutoFocus={() => {\n const activeElement = document.activeElement;\n if (\n activeElement instanceof HTMLElement &&\n activeElement !== document.body &&\n !activeElement.closest('[role=\"dialog\"]')\n ) {\n returnFocus.current = activeElement;\n }\n }}\n onCloseAutoFocus={(event) => {\n event.preventDefault();\n if (returnFocus.current?.isConnected) returnFocus.current.focus();\n }}\n className=\"flex w-full md:w-[calc(100%-2rem)] max-w-5xl flex-col gap-0 overflow-hidden rounded-card bg-bg-surface p-0 sm:max-w-5xl\"\n style={{ maxHeight: 'calc(100dvh - 2rem)' }}\n >\n <DialogDescription className=\"sr-only\">\n Explore the linked dashboard and return to the previous drilldown level.\n </DialogDescription>\n {/* Header — bare chrome: identity + close, no surface of its own */}\n <div className=\"flex flex-shrink-0 flex-wrap items-center justify-between gap-control-gap border-b border-[var(--color-border-subtle)] px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n {/* Back button */}\n {history.length > 0 && (\n <button\n type=\"button\"\n onClick={handleBack}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] shrink-0 items-center justify-center rounded-button motion-safe:transition-colors hover:bg-bg-sunken ${SECTION_FOCUS_RING}`}\n title=\"Go back\"\n aria-label=\"Go back\"\n >\n <svg className=\"w-5 h-5 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n )}\n\n {/* Title */}\n <DialogTitle className=\"truncate text-sm font-semibold text-text-primary\">Drilldown View</DialogTitle>\n\n {/* Breadcrumb indicator */}\n {history.length > 0 && (\n <span className=\"text-xs text-text-muted\">\n ({history.length + 1} level{history.length > 0 ? 's' : ''} deep)\n </span>\n )}\n </div>\n\n {/* Close button */}\n <button\n type=\"button\"\n onClick={handleClose}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] shrink-0 items-center justify-center rounded-button motion-safe:transition-colors hover:bg-bg-sunken ${SECTION_FOCUS_RING}`}\n title=\"Close (Esc)\"\n aria-label=\"Close drilldown\"\n >\n <svg className=\"w-5 h-5 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Dashboard Content — the point of the overlay, so it gets the surface\n weight and the chrome around it stays flat. */}\n <div className=\"min-h-0 flex-1 overflow-y-auto bg-bg-sunken p-4\">\n {renderDashboard ? renderDashboard(dashboardId, params) : <DrilldownFallback />}\n </div>\n\n {/* Footer — supporting detail only */}\n <div className=\"flex-shrink-0 border-t border-[var(--color-border-subtle)] px-4 py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-x-4 gap-y-1 text-xs text-text-muted\">\n <span>\n Parameters:{' '}\n {
|
|
1
|
+
{"version":3,"file":"DrilldownModal.js","names":[],"sources":["../../../src/bigconsole/components/DrilldownModal.tsx"],"sourcesContent":["/**\n * DrilldownModal Component\n *\n * Modal overlay for displaying drilldown dashboards.\n * Supports nested drilldowns with back navigation.\n */\n\nimport { type FC, useCallback, useRef, memo } from 'react';\nimport { useDrilldownStore } from '../store/drilldownStore';\nimport { SECTION_FOCUS_RING } from './common';\nimport { getVisibleDrilldownParams } from '../utils/drilldownParams';\nimport {\n ResponsiveDialog as Dialog,\n ResponsiveDialogContent as DialogContent,\n ResponsiveDialogTitle as DialogTitle,\n ResponsiveDialogDescription as DialogDescription,\n} from '@burdenoff/fe-libs/ui';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DrilldownModalProps {\n /** Custom content renderer for the dashboard */\n renderDashboard?: (dashboardId: string, params: Record<string, unknown>) => React.ReactNode;\n /** Called when modal closes */\n onClose?: () => void;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DrilldownModal: FC<DrilldownModalProps> = memo(function DrilldownModal({ renderDashboard, onClose }) {\n const returnFocus = useRef<HTMLElement | null>(null);\n const isOpen = useDrilldownStore((state) => state.isOpen);\n const dashboardId = useDrilldownStore((state) => state.dashboardId);\n const params = useDrilldownStore((state) => state.params);\n const history = useDrilldownStore((state) => state.history);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n const popDrilldown = useDrilldownStore((state) => state.popDrilldown);\n\n // Define handlers before effects that use them\n const handleClose = useCallback(() => {\n closeModal();\n onClose?.();\n }, [closeModal, onClose]);\n\n const handleBack = useCallback(() => {\n popDrilldown();\n }, [popDrilldown]);\n\n if (!isOpen || !dashboardId) {\n return null;\n }\n\n return (\n <Dialog\n open={isOpen}\n onOpenChange={(open) => {\n if (!open) handleClose();\n }}\n >\n <DialogContent\n showCloseButton={false}\n onOpenAutoFocus={() => {\n const activeElement = document.activeElement;\n if (\n activeElement instanceof HTMLElement &&\n activeElement !== document.body &&\n !activeElement.closest('[role=\"dialog\"]')\n ) {\n returnFocus.current = activeElement;\n }\n }}\n onCloseAutoFocus={(event) => {\n event.preventDefault();\n if (returnFocus.current?.isConnected) returnFocus.current.focus();\n }}\n className=\"flex w-full md:w-[calc(100%-2rem)] max-w-5xl flex-col gap-0 overflow-hidden rounded-card bg-bg-surface p-0 sm:max-w-5xl\"\n style={{ maxHeight: 'calc(100dvh - 2rem)' }}\n >\n <DialogDescription className=\"sr-only\">\n Explore the linked dashboard and return to the previous drilldown level.\n </DialogDescription>\n {/* Header — bare chrome: identity + close, no surface of its own */}\n <div className=\"flex flex-shrink-0 flex-wrap items-center justify-between gap-control-gap border-b border-[var(--color-border-subtle)] px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-3\">\n {/* Back button */}\n {history.length > 0 && (\n <button\n type=\"button\"\n onClick={handleBack}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] shrink-0 items-center justify-center rounded-button motion-safe:transition-colors hover:bg-bg-sunken ${SECTION_FOCUS_RING}`}\n title=\"Go back\"\n aria-label=\"Go back\"\n >\n <svg className=\"w-5 h-5 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n )}\n\n {/* Title */}\n <DialogTitle className=\"truncate text-sm font-semibold text-text-primary\">Drilldown View</DialogTitle>\n\n {/* Breadcrumb indicator */}\n {history.length > 0 && (\n <span className=\"text-xs text-text-muted\">\n ({history.length + 1} level{history.length > 0 ? 's' : ''} deep)\n </span>\n )}\n </div>\n\n {/* Close button */}\n <button\n type=\"button\"\n onClick={handleClose}\n className={`flex h-control-md w-[var(--size-controlHeight-md)] shrink-0 items-center justify-center rounded-button motion-safe:transition-colors hover:bg-bg-sunken ${SECTION_FOCUS_RING}`}\n title=\"Close (Esc)\"\n aria-label=\"Close drilldown\"\n >\n <svg className=\"w-5 h-5 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Dashboard Content — the point of the overlay, so it gets the surface\n weight and the chrome around it stays flat. */}\n <div className=\"min-h-0 flex-1 overflow-y-auto bg-bg-sunken p-4\">\n {renderDashboard ? renderDashboard(dashboardId, params) : <DrilldownFallback />}\n </div>\n\n {/* Footer — supporting detail only */}\n <div className=\"flex-shrink-0 border-t border-[var(--color-border-subtle)] px-4 py-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-x-4 gap-y-1 text-xs text-text-muted\">\n <span>\n Parameters:{' '}\n {getVisibleDrilldownParams(params)\n .map(([key]) => key)\n .join(', ') || 'None'}\n </span>\n <span>Press Esc to close</span>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n );\n});\n\n// Fallback when no renderDashboard prop is provided (should not happen in normal usage\n// since BigConsoleRoot always passes DrilldownDashboardRenderer).\nfunction DrilldownFallback() {\n return (\n <div className=\"flex items-center justify-center h-full text-sm text-text-secondary\">\n Unable to load drilldown content. Please try again.\n </div>\n );\n}\n\nexport default DrilldownModal;\n"],"mappings":";;;;;;;;AAiCA,IAAa,IAA0C,EAAK,SAAwB,EAAE,oBAAiB,cAAW;CAChH,IAAM,IAAc,EAA2B,KAAK,EAC9C,IAAS,GAAmB,MAAU,EAAM,OAAO,EACnD,IAAc,GAAmB,MAAU,EAAM,YAAY,EAC7D,IAAS,GAAmB,MAAU,EAAM,OAAO,EACnD,IAAU,GAAmB,MAAU,EAAM,QAAQ,EACrD,IAAa,GAAmB,MAAU,EAAM,WAAW,EAC3D,IAAe,GAAmB,MAAU,EAAM,aAAa,EAG/D,IAAc,QAAkB;AAEpC,EADA,GAAY,EACZ,KAAW;IACV,CAAC,GAAY,EAAQ,CAAC,EAEnB,IAAa,QAAkB;AACnC,KAAc;IACb,CAAC,EAAa,CAAC;AAMlB,QAJI,CAAC,KAAU,CAAC,IACP,OAIP,kBAAC,GAAD;EACE,MAAM;EACN,eAAe,MAAS;AACtB,GAAK,KAAM,GAAa;;YAG1B,kBAAC,GAAD;GACE,iBAAiB;GACjB,uBAAuB;IACrB,IAAM,IAAgB,SAAS;AAC/B,IACE,aAAyB,eACzB,MAAkB,SAAS,QAC3B,CAAC,EAAc,QAAQ,oBAAkB,KAEzC,EAAY,UAAU;;GAG1B,mBAAmB,MAAU;AAE3B,IADA,EAAM,gBAAgB,EAClB,EAAY,SAAS,eAAa,EAAY,QAAQ,OAAO;;GAEnE,WAAU;GACV,OAAO,EAAE,WAAW,uBAAuB;aAjB7C;IAmBE,kBAAC,GAAD;KAAmB,WAAU;eAAU;KAEnB,CAAA;IAEpB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OAEG,EAAQ,SAAS,KAChB,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,WAAW;QACX,OAAM;QACN,cAAW;kBAEX,kBAAC,OAAD;SAAK,WAAU;SAA8B,MAAK;SAAO,QAAO;SAAe,SAAQ;mBACrF,kBAAC,QAAD;UAAM,eAAc;UAAQ,gBAAe;UAAQ,aAAa;UAAG,GAAE;UAAoB,CAAA;SACrF,CAAA;QACC,CAAA;OAIX,kBAAC,GAAD;QAAa,WAAU;kBAAmD;QAA4B,CAAA;OAGrG,EAAQ,SAAS,KAChB,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SAA0C;SACtC,EAAQ,SAAS;SAAE;SAAO,EAAQ,SAAS,IAAI,MAAM;SAAG;SACrD;;OAEL;SAGN,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAW,2JAA2J;MACtK,OAAM;MACN,cAAW;gBAEX,kBAAC,OAAD;OAAK,WAAU;OAA8B,MAAK;OAAO,QAAO;OAAe,SAAQ;iBACrF,kBAAC,QAAD;QAAM,eAAc;QAAQ,gBAAe;QAAQ,aAAa;QAAG,GAAE;QAAyB,CAAA;OAC1F,CAAA;MACC,CAAA,CACL;;IAIN,kBAAC,OAAD;KAAK,WAAU;eACZ,IAAkB,EAAgB,GAAa,EAAO,GAAG,kBAAC,GAAD,EAAqB,CAAA;KAC3E,CAAA;IAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD,EAAA,UAAA;OAAM;OACQ;OACX,EAA0B,EAAO,CAC/B,KAAK,CAAC,OAAS,EAAI,CACnB,KAAK,KAAK,IAAI;OACZ,EAAA,CAAA,EACP,kBAAC,QAAD,EAAA,UAAM,sBAAyB,CAAA,CAC3B;;KACF,CAAA;IACQ;;EACT,CAAA;EAEX;AAIF,SAAS,IAAoB;AAC3B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAsE;EAE/E,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drilldownParams.d.ts","sourceRoot":"","sources":["../../../src/bigconsole/utils/drilldownParams.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOnG"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/bigconsole/utils/drilldownParams.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return Object.entries(e).filter(([e, t]) => e.startsWith("__") || t == null ? !1 : typeof t == "string" ? t.trim().length > 0 : Array.isArray(t) ? t.length > 0 : !0);
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
export { e as getVisibleDrilldownParams };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=drilldownParams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drilldownParams.js","names":[],"sources":["../../../src/bigconsole/utils/drilldownParams.ts"],"sourcesContent":["/** Display only meaningful context; the original drilldown parameters remain unchanged. */\nexport function getVisibleDrilldownParams(params: Record<string, unknown>): Array<[string, unknown]> {\n return Object.entries(params).filter(([key, value]) => {\n if (key.startsWith('__') || value === null || value === undefined) return false;\n if (typeof value === 'string') return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n return true;\n });\n}\n"],"mappings":";AACA,SAAgB,EAA0B,GAA2D;AACnG,QAAO,OAAO,QAAQ,EAAO,CAAC,QAAQ,CAAC,GAAK,OACtC,EAAI,WAAW,KAAK,IAAI,KAAU,OAAoC,KACtE,OAAO,KAAU,WAAiB,EAAM,MAAM,CAAC,SAAS,IACxD,MAAM,QAAQ,EAAM,GAAS,EAAM,SAAS,IACzC,GACP"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-bigconsole",
|
|
3
|
-
"version": "2026.1004.
|
|
3
|
+
"version": "2026.1004.5",
|
|
4
4
|
"description": "BigConsole - AI-powered analytics and dashboard platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./styles.css": "./dist/microfe-bigconsole.css",
|
|
16
17
|
"./assistant": {
|
|
17
18
|
"types": "./dist/bigconsole/assistant/index.d.ts",
|
|
18
19
|
"import": "./dist/bigconsole/assistant/index.js"
|