@juicemantics/veloiq-ui 0.8.5 → 0.9.1

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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import React5, { createContext, lazy, useContext, useMemo, useState, useRef, useEffect, useCallback, useLayoutEffect, useSyncExternalStore, Suspense, useId, useImperativeHandle } from 'react';
1
+ import React6, { createContext, lazy, useContext, useMemo, useState, useRef, useEffect, useCallback, useLayoutEffect, useSyncExternalStore, Suspense, useId, useImperativeHandle } from 'react';
2
2
  import { ThemedLayoutV2, Show, List, useForm, DeleteButton, useTable, RefineThemes, Breadcrumb as Breadcrumb$1, Create, useSelect, Edit, ListButton, EditButton, RefreshButton } from '@refinedev/antd';
3
3
  import { useMenu, useGo, useGetIdentity, useLogout, useOne, useApiUrl, useCan, useInvalidate, useCustom, useLogin, useWarnAboutChange } from '@refinedev/core';
4
4
  import { Typography, Menu, theme, Layout, Space, AutoComplete, Input, Spin, ConfigProvider, Divider, Row, Col, Card, Grid, Form, Drawer, Modal, Button, Skeleton, Tooltip, message, Switch, Tabs, Alert, Empty, Collapse, Select, Table, DatePicker, InputNumber, Checkbox, Pagination, Breadcrumb, Tree, Tag, List as List$1, Popover, Dropdown, Avatar, TimePicker, Upload, Rate, Progress } from 'antd';
@@ -20,8 +20,6 @@ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot
20
20
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
21
21
  var ColorModeContext = createContext({ mode: "light", setMode: () => {
22
22
  }, schemaVersion: 0 });
23
-
24
- // src/utils/modelTone.ts
25
23
  var MODEL_TONES_LIGHT = [
26
24
  { solid: "#2563eb", soft: "#dbeafe", softer: "#eff6ff", text: "#1e3a8a", border: "#93c5fd", shadow: "rgba(37, 99, 235, 0.22)" },
27
25
  { solid: "#0f766e", soft: "#ccfbf1", softer: "#f0fdfa", text: "#115e59", border: "#5eead4", shadow: "rgba(15, 118, 110, 0.22)" },
@@ -138,6 +136,10 @@ var setColorSchemas = (schemas) => {
138
136
  const darkBgContent = `rgb(${shade(r, 0.06)}, ${shade(g, 0.06)}, ${shade(b, 0.06)})`;
139
137
  const darkBgElements = `rgb(${shade(r, 0.11)}, ${shade(g, 0.11)}, ${shade(b, 0.11)})`;
140
138
  const darkBgHover = `rgb(${shade(r, 0.16)}, ${shade(g, 0.16)}, ${shade(b, 0.16)})`;
139
+ const sidebarLightBg = hex;
140
+ const sidebarLightText = isDarkColor(hex) ? "#ffffff" : "#0f172a";
141
+ const sidebarDarkBg = lightBgElements;
142
+ const sidebarDarkText = "#0f172a";
141
143
  styleEl.innerHTML = `
142
144
  /* --- LIGHT MODE OVERRIDES --- */
143
145
  body.jm-light .ant-layout,
@@ -151,7 +153,31 @@ var setColorSchemas = (schemas) => {
151
153
  body.jm-light .ant-menu,
152
154
  body.jm-light .ant-menu-submenu,
153
155
  body.jm-light .ant-menu-submenu-title,
154
- body.jm-light .ant-layout-header,
156
+ body.jm-light .ant-layout-header {
157
+ background-color: ${sidebarLightBg} !important;
158
+ color: ${sidebarLightText} !important;
159
+ }
160
+ body.jm-light .ant-layout-sider *:not(input):not(textarea):not(.ant-input):not(.ant-select-selector):not(.ant-select-selection-item):not(.ant-select-selection-search-input),
161
+ body.jm-light .ant-layout-header *:not(input):not(textarea):not(.ant-input):not(.ant-select-selector):not(.ant-select-selection-item):not(.ant-select-selection-search-input) {
162
+ color: ${sidebarLightText} !important;
163
+ }
164
+ body.jm-light .ant-layout-sider .ant-menu-item-selected,
165
+ body.jm-light .ant-layout-sider .ant-menu-item-selected *,
166
+ body.jm-light .ant-layout-sider .ant-menu-item:hover,
167
+ body.jm-light .ant-layout-sider .ant-menu-item:hover *,
168
+ body.jm-light .ant-layout-header .ant-menu-item-selected,
169
+ body.jm-light .ant-layout-header .ant-menu-item-selected *,
170
+ body.jm-light .ant-layout-header .ant-menu-item:hover,
171
+ body.jm-light .ant-layout-header .ant-menu-item:hover * {
172
+ color: revert !important;
173
+ }
174
+ body.jm-light .ant-menu-submenu-popup {
175
+ background-color: ${sidebarLightBg} !important;
176
+ }
177
+ body.jm-light .ant-menu-submenu-popup .ant-menu-item:not(.ant-menu-item-selected),
178
+ body.jm-light .ant-menu-submenu-popup .ant-menu-submenu-title:not(.ant-menu-submenu-selected) {
179
+ color: ${sidebarLightText} !important;
180
+ }
155
181
  body.jm-light .ant-card,
156
182
  body.jm-light .ant-table-wrapper .ant-table,
157
183
  body.jm-light .ant-table-thead > tr > th,
@@ -181,7 +207,31 @@ var setColorSchemas = (schemas) => {
181
207
  body.jm-dark .ant-menu,
182
208
  body.jm-dark .ant-menu-submenu,
183
209
  body.jm-dark .ant-menu-submenu-title,
184
- body.jm-dark .ant-layout-header,
210
+ body.jm-dark .ant-layout-header {
211
+ background-color: ${sidebarDarkBg} !important;
212
+ color: ${sidebarDarkText} !important;
213
+ }
214
+ body.jm-dark .ant-layout-sider *:not(input):not(textarea):not(.ant-input):not(.ant-select-selector):not(.ant-select-selection-item):not(.ant-select-selection-search-input),
215
+ body.jm-dark .ant-layout-header *:not(input):not(textarea):not(.ant-input):not(.ant-select-selector):not(.ant-select-selection-item):not(.ant-select-selection-search-input) {
216
+ color: ${sidebarDarkText} !important;
217
+ }
218
+ body.jm-dark .ant-layout-sider .ant-menu-item-selected,
219
+ body.jm-dark .ant-layout-sider .ant-menu-item-selected *,
220
+ body.jm-dark .ant-layout-sider .ant-menu-item:hover,
221
+ body.jm-dark .ant-layout-sider .ant-menu-item:hover *,
222
+ body.jm-dark .ant-layout-header .ant-menu-item-selected,
223
+ body.jm-dark .ant-layout-header .ant-menu-item-selected *,
224
+ body.jm-dark .ant-layout-header .ant-menu-item:hover,
225
+ body.jm-dark .ant-layout-header .ant-menu-item:hover * {
226
+ color: revert !important;
227
+ }
228
+ body.jm-dark .ant-menu-submenu-popup {
229
+ background-color: ${sidebarDarkBg} !important;
230
+ }
231
+ body.jm-dark .ant-menu-submenu-popup .ant-menu-item:not(.ant-menu-item-selected),
232
+ body.jm-dark .ant-menu-submenu-popup .ant-menu-submenu-title:not(.ant-menu-submenu-selected) {
233
+ color: ${sidebarDarkText} !important;
234
+ }
185
235
  body.jm-dark .ant-card,
186
236
  body.jm-dark .ant-table-wrapper .ant-table,
187
237
  body.jm-dark .ant-table-thead > tr > th,
@@ -281,7 +331,7 @@ function guessIcon(text, isModule = false) {
281
331
  function resolveIcon(iconName) {
282
332
  const registry = AntDIcons2;
283
333
  const IconCls = registry[iconName];
284
- return IconCls ? React5.createElement(IconCls) : React5.createElement(registry["TableOutlined"]);
334
+ return IconCls ? React6.createElement(IconCls) : React6.createElement(registry["TableOutlined"]);
285
335
  }
286
336
  function getNavEntry(navConfig, key) {
287
337
  return navConfig.find((e) => e.key === key);
@@ -369,6 +419,7 @@ function injectJourneyMenuItems(items, byModule) {
369
419
  return walk(items);
370
420
  }
371
421
  var HorizontalMenu = ({ navConfig = [] }) => {
422
+ const { mode } = useContext(ColorModeContext);
372
423
  const { menuItems, selectedKey } = useMenu();
373
424
  const go = useGo();
374
425
  const journeysByModule = useJourneyMenuItems();
@@ -394,7 +445,7 @@ var HorizontalMenu = ({ navConfig = [] }) => {
394
445
  const renderLabel = (item, depth, hasChildren) => {
395
446
  const label = String(item?.label || item?.name || item?.key || "");
396
447
  const isModule = depth === 0 || hasChildren;
397
- const tone = isModule ? getModelTone(`module:${item?.key || label}`) : getModelTone(resolveModelSeed(item));
448
+ isModule ? getModelTone(`module:${item?.key || label}`) : getModelTone(resolveModelSeed(item));
398
449
  return /* @__PURE__ */ jsx(
399
450
  "span",
400
451
  {
@@ -404,7 +455,7 @@ var HorizontalMenu = ({ navConfig = [] }) => {
404
455
  padding: isModule ? "2px 5px" : "1px 5px",
405
456
  borderRadius: 8,
406
457
  background: "transparent",
407
- color: tone.text,
458
+ color: "inherit",
408
459
  fontWeight: 400
409
460
  },
410
461
  children: label
@@ -430,6 +481,7 @@ var HorizontalMenu = ({ navConfig = [] }) => {
430
481
  Menu,
431
482
  {
432
483
  mode: "horizontal",
484
+ theme: mode === "dark" ? "light" : "dark",
433
485
  selectedKeys: [selectedKey],
434
486
  items,
435
487
  style: {
@@ -468,7 +520,7 @@ var CustomSider = ({ collapsed, logo, appTitle, navConfig = [] }) => {
468
520
  const renderLabel = (item, depth, hasChildren) => {
469
521
  const label = String(item?.label || item?.name || item?.key || "");
470
522
  const isModule = depth === 0 || hasChildren;
471
- const tone = isModule ? getModelTone(`module:${item?.key || label}`) : getModelTone(resolveModelSeed(item));
523
+ isModule ? getModelTone(`module:${item?.key || label}`) : getModelTone(resolveModelSeed(item));
472
524
  return /* @__PURE__ */ jsx(
473
525
  "span",
474
526
  {
@@ -478,7 +530,7 @@ var CustomSider = ({ collapsed, logo, appTitle, navConfig = [] }) => {
478
530
  padding: isModule ? "3px 8px" : "2px 8px",
479
531
  borderRadius: 8,
480
532
  background: "transparent",
481
- color: tone.text,
533
+ color: "inherit",
482
534
  fontWeight: 400
483
535
  },
484
536
  children: label
@@ -731,6 +783,7 @@ var GlobalSearch = () => {
731
783
  const navigate = useNavigate();
732
784
  const [searchText, setSearchText] = useState("");
733
785
  const { results: backendResults, searching, search, clear } = useRecordSearch();
786
+ const { token } = theme.useToken();
734
787
  const searchableResources = useMemo(() => flattenMenuItems(menuItems), [menuItems]);
735
788
  const resourceResults = useMemo(() => {
736
789
  const q2 = searchText.toLowerCase().trim();
@@ -820,7 +873,7 @@ var GlobalSearch = () => {
820
873
  suffix: !focused ? /* @__PURE__ */ jsx(Typography.Text, { type: "secondary", style: { fontSize: 9 }, children: "\u2303K" }) : void 0,
821
874
  allowClear: true,
822
875
  size: "small",
823
- style: !focused ? { paddingInline: 2 } : void 0
876
+ style: !focused ? { paddingInline: 2, color: token.colorText } : { color: token.colorText }
824
877
  }
825
878
  )
826
879
  }
@@ -1276,7 +1329,7 @@ var CommandCenterPortal = ({
1276
1329
  /* @__PURE__ */ jsx(Typography.Text, { style: SECTION_LABEL_STYLE, children: "Pinned" }),
1277
1330
  pinnedLoading && /* @__PURE__ */ jsx(Spin, { size: "small", style: { marginLeft: "auto" } })
1278
1331
  ] }),
1279
- pinnedItems.length > 0 && /* @__PURE__ */ jsx("div", { style: { padding: "6px 10px", overflowY: "auto", maxHeight: 400 }, children: pinnedItems.map((item, idx) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1332
+ pinnedItems.length > 0 && /* @__PURE__ */ jsx("div", { style: { padding: "6px 10px", overflowY: "auto", maxHeight: 400 }, children: pinnedItems.map((item, idx) => /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1280
1333
  idx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "2px 0", borderColor: "rgba(255,255,255,0.04)" } }),
1281
1334
  renderRow(
1282
1335
  `pin-${item.resource}-${item.id}`,
@@ -1299,7 +1352,7 @@ var CommandCenterPortal = ({
1299
1352
  /* @__PURE__ */ jsx(ClockCircleOutlined, { style: { color: "rgba(255,255,255,0.45)", fontSize: 12 } }),
1300
1353
  /* @__PURE__ */ jsx(Typography.Text, { style: SECTION_LABEL_STYLE, children: "Recent" })
1301
1354
  ] }),
1302
- /* @__PURE__ */ jsx("div", { style: { padding: "6px 10px", overflowY: "auto", maxHeight: 400 }, children: recentItems.map((item, idx) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1355
+ /* @__PURE__ */ jsx("div", { style: { padding: "6px 10px", overflowY: "auto", maxHeight: 400 }, children: recentItems.map((item, idx) => /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1303
1356
  idx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "2px 0", borderColor: "rgba(255,255,255,0.04)" } }),
1304
1357
  renderRow(
1305
1358
  `recent-${item.resource}-${item.id}`,
@@ -1328,7 +1381,7 @@ var CommandCenterPortal = ({
1328
1381
  const moduleLabel = String(module.label || module.name || "");
1329
1382
  const tone = getModelTone(moduleKey);
1330
1383
  const children = navConfig.length > 0 ? sortItemsByNavConfig(module.children || [], navConfig) : module.children || [];
1331
- return /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1384
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1332
1385
  modIdx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "5px 0", borderColor: "rgba(255,255,255,0.06)" } }),
1333
1386
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, padding: "4px 8px 2px" }, children: [
1334
1387
  /* @__PURE__ */ jsx("span", { style: { color: tone.solid, fontSize: 12, display: "flex", alignItems: "center" }, children: getItemIcon(moduleKey, moduleLabel, true) }),
@@ -1339,7 +1392,7 @@ var CommandCenterPortal = ({
1339
1392
  const childLabel = String(child.label || child.name || "");
1340
1393
  const childTone = getModelTone(childKey);
1341
1394
  const childIcon = getItemIcon(childKey, childLabel, false, child.icon);
1342
- return /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1395
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1343
1396
  idx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "2px 0", borderColor: "rgba(255,255,255,0.04)" } }),
1344
1397
  /* @__PURE__ */ jsxs(
1345
1398
  "div",
@@ -1391,7 +1444,7 @@ var CommandCenterPortal = ({
1391
1444
  const moduleLabel = child.moduleLabel || "";
1392
1445
  const navId = `cmd-${childKey}`;
1393
1446
  const active = isActive(navId);
1394
- return /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1447
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1395
1448
  idx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "2px 0", borderColor: "rgba(255,255,255,0.05)" } }),
1396
1449
  /* @__PURE__ */ jsxs(
1397
1450
  "div",
@@ -1435,13 +1488,13 @@ var CommandCenterPortal = ({
1435
1488
  ] }),
1436
1489
  backendResults.length > 0 && /* @__PURE__ */ jsx("div", { style: { padding: "6px 10px" }, children: backendResults.map((modelResult, modelIdx) => {
1437
1490
  const tone = getModelTone(modelResult.resource);
1438
- return /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1491
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1439
1492
  modelIdx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "4px 0", borderColor: "rgba(255,255,255,0.05)" } }),
1440
1493
  /* @__PURE__ */ jsx(Typography.Text, { style: { color: "rgba(255,255,255,0.35)", fontSize: 10, textTransform: "uppercase", letterSpacing: "0.07em", display: "block", padding: "4px 10px 2px" }, children: modelResult.modelLabel }),
1441
1494
  modelResult.records.map((record, recIdx) => {
1442
1495
  const navId = `record-${modelResult.resource}-${record.id}`;
1443
1496
  const active = isActive(navId);
1444
- return /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1497
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1445
1498
  recIdx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "2px 0", borderColor: "rgba(255,255,255,0.04)" } }),
1446
1499
  /* @__PURE__ */ jsxs(
1447
1500
  "div",
@@ -1523,7 +1576,7 @@ var CommandCenterPortal = ({
1523
1576
  const childLabel = String(child.label || child.name || "");
1524
1577
  const childTone = getModelTone(childKey);
1525
1578
  const childIcon = getItemIcon(childKey, childLabel, false, child.icon);
1526
- return /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1579
+ return /* @__PURE__ */ jsxs(React6.Fragment, { children: [
1527
1580
  idx > 0 && /* @__PURE__ */ jsx(Divider, { style: { margin: "2px 0", borderColor: "rgba(255,255,255,0.05)" } }),
1528
1581
  /* @__PURE__ */ jsxs(
1529
1582
  "div",
@@ -4648,7 +4701,7 @@ var extractButtonLabel = (node) => {
4648
4701
  }
4649
4702
  return null;
4650
4703
  }
4651
- if (React5.isValidElement(node)) {
4704
+ if (React6.isValidElement(node)) {
4652
4705
  return extractButtonLabel(node.props?.children);
4653
4706
  }
4654
4707
  return null;
@@ -4665,14 +4718,14 @@ var renderIconOnlyButtons = (nodes) => {
4665
4718
  const enhanceNode = (node, index) => {
4666
4719
  if (node === null || node === void 0 || typeof node === "boolean") return node;
4667
4720
  if (Array.isArray(node)) return node.map((child, childIndex) => enhanceNode(child, childIndex));
4668
- if (!React5.isValidElement(node)) return node;
4721
+ if (!React6.isValidElement(node)) return node;
4669
4722
  const componentName = node.type?.displayName || node.type?.name;
4670
4723
  if (componentName === "RefreshButton") return null;
4671
4724
  const fallbackLabel = componentName ? fallbackLabels[componentName] : null;
4672
4725
  const nodeProps = node.props;
4673
4726
  if (fallbackLabel) {
4674
4727
  const label = extractButtonLabel(nodeProps?.children) || fallbackLabel;
4675
- const element = React5.cloneElement(node, {
4728
+ const element = React6.cloneElement(node, {
4676
4729
  ...nodeProps,
4677
4730
  hideText: true,
4678
4731
  children: null
@@ -4683,7 +4736,7 @@ var renderIconOnlyButtons = (nodes) => {
4683
4736
  if (nodeProps?.icon) {
4684
4737
  const label = extractButtonLabel(nodeProps?.children);
4685
4738
  if (label) {
4686
- const element = React5.cloneElement(node, {
4739
+ const element = React6.cloneElement(node, {
4687
4740
  ...nodeProps,
4688
4741
  children: null
4689
4742
  });
@@ -4691,15 +4744,15 @@ var renderIconOnlyButtons = (nodes) => {
4691
4744
  }
4692
4745
  }
4693
4746
  if (nodeProps?.children) {
4694
- const mappedChildren = React5.Children.map(nodeProps.children, (child, childIndex) => enhanceNode(child, childIndex));
4695
- return React5.cloneElement(node, {
4747
+ const mappedChildren = React6.Children.map(nodeProps.children, (child, childIndex) => enhanceNode(child, childIndex));
4748
+ return React6.cloneElement(node, {
4696
4749
  ...nodeProps,
4697
4750
  children: mappedChildren
4698
4751
  });
4699
4752
  }
4700
4753
  return node;
4701
4754
  };
4702
- return React5.Children.map(nodes, (child, index) => enhanceNode(child, index));
4755
+ return React6.Children.map(nodes, (child, index) => enhanceNode(child, index));
4703
4756
  };
4704
4757
  var ResponsiveHeaderButtons = ({ children }) => {
4705
4758
  const screens = Grid.useBreakpoint();
@@ -4750,7 +4803,7 @@ var extractButtonLabel2 = (node) => {
4750
4803
  }
4751
4804
  return null;
4752
4805
  }
4753
- if (React5.isValidElement(node)) {
4806
+ if (React6.isValidElement(node)) {
4754
4807
  return extractButtonLabel2(node.props?.children);
4755
4808
  }
4756
4809
  return null;
@@ -4768,12 +4821,12 @@ var renderIconOnlyButtons2 = (nodes) => {
4768
4821
  const enhanceNode = (node, index) => {
4769
4822
  if (node === null || node === void 0 || typeof node === "boolean") return node;
4770
4823
  if (Array.isArray(node)) return node.map((child, childIndex) => enhanceNode(child, childIndex));
4771
- if (!React5.isValidElement(node)) return node;
4824
+ if (!React6.isValidElement(node)) return node;
4772
4825
  const componentName = node.type?.displayName || node.type?.name;
4773
4826
  const fallbackLabel = componentName ? fallbackLabels[componentName] : null;
4774
4827
  if (fallbackLabel) {
4775
4828
  const label = extractButtonLabel2(node.props?.children) || fallbackLabel;
4776
- const element = React5.cloneElement(node, {
4829
+ const element = React6.cloneElement(node, {
4777
4830
  ...node.props,
4778
4831
  hideText: true,
4779
4832
  children: null
@@ -4784,7 +4837,7 @@ var renderIconOnlyButtons2 = (nodes) => {
4784
4837
  if (node.props?.icon) {
4785
4838
  const label = extractButtonLabel2(node.props?.children);
4786
4839
  if (label) {
4787
- const element = React5.cloneElement(node, {
4840
+ const element = React6.cloneElement(node, {
4788
4841
  ...node.props,
4789
4842
  children: null
4790
4843
  });
@@ -4792,15 +4845,15 @@ var renderIconOnlyButtons2 = (nodes) => {
4792
4845
  }
4793
4846
  }
4794
4847
  if (node.props?.children) {
4795
- const mappedChildren = React5.Children.map(node.props.children, (child, childIndex) => enhanceNode(child, childIndex));
4796
- return React5.cloneElement(node, {
4848
+ const mappedChildren = React6.Children.map(node.props.children, (child, childIndex) => enhanceNode(child, childIndex));
4849
+ return React6.cloneElement(node, {
4797
4850
  ...node.props,
4798
4851
  children: mappedChildren
4799
4852
  });
4800
4853
  }
4801
4854
  return node;
4802
4855
  };
4803
- return React5.Children.map(nodes, (child, index) => enhanceNode(child, index));
4856
+ return React6.Children.map(nodes, (child, index) => enhanceNode(child, index));
4804
4857
  };
4805
4858
  var renderStandardShowHeaderButtons = ({
4806
4859
  listButtonProps,
@@ -4964,7 +5017,7 @@ var wrappedPageTitleStyle2 = {
4964
5017
  };
4965
5018
  var renderWrappedPageTitle = (title) => {
4966
5019
  if (title === null || title === void 0 || title === false) return title;
4967
- return React5.createElement("div", { style: wrappedPageTitleStyle2 }, title);
5020
+ return React6.createElement("div", { style: wrappedPageTitleStyle2 }, title);
4968
5021
  };
4969
5022
  var numberFormatter = new Intl.NumberFormat(void 0, { maximumFractionDigits: 0 });
4970
5023
  var decimalFormatter = new Intl.NumberFormat(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
@@ -5080,6 +5133,39 @@ var getSortPriority = (columnSort, fieldKey) => {
5080
5133
  const index = columnSort.findIndex((item) => item.fieldKey === fieldKey);
5081
5134
  return index === -1 ? 1 : columnSort.length - index + 1;
5082
5135
  };
5136
+ var _TOKEN_KEY = "jm_access_token";
5137
+ function useAuthenticatedFileUrl(rawUrl) {
5138
+ const [src, setSrc] = useState("");
5139
+ useEffect(() => {
5140
+ if (!rawUrl) {
5141
+ setSrc("");
5142
+ return;
5143
+ }
5144
+ if (!rawUrl.includes("/api/file/")) {
5145
+ setSrc(rawUrl);
5146
+ return;
5147
+ }
5148
+ const token = localStorage.getItem(_TOKEN_KEY) || "";
5149
+ const controller = new AbortController();
5150
+ let objectUrl = "";
5151
+ fetch(rawUrl, {
5152
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
5153
+ signal: controller.signal
5154
+ }).then((r) => r.ok ? r.blob() : Promise.reject()).then((blob) => {
5155
+ objectUrl = URL.createObjectURL(blob);
5156
+ setSrc(objectUrl);
5157
+ }).catch(() => setSrc(""));
5158
+ return () => {
5159
+ controller.abort();
5160
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
5161
+ };
5162
+ }, [rawUrl]);
5163
+ return src;
5164
+ }
5165
+ var AuthenticatedImage = ({ url, alt, style }) => {
5166
+ const src = useAuthenticatedFileUrl(url);
5167
+ return src ? /* @__PURE__ */ jsx("img", { src, alt: alt ?? "", style }) : null;
5168
+ };
5083
5169
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "tif", "tiff"]);
5084
5170
  var isImageRecord = (record) => {
5085
5171
  if (record?.avatar_url || record?.image_url || record?.photo_url) return true;
@@ -5137,7 +5223,7 @@ var renderSharedGalleryCard = ({
5137
5223
  style: { width: imageWidth, display: "grid", gap: 6, cursor: onClick ? "pointer" : "default" },
5138
5224
  onClick,
5139
5225
  children: [
5140
- contentUrl ? /* @__PURE__ */ jsx("img", { src: contentUrl, alt: label, style: imageStyle }) : /* @__PURE__ */ jsx("div", { style: { ...imageStyle, display: "flex", alignItems: "center", justifyContent: "center", color: "#8c8c8c" }, children: /* @__PURE__ */ jsx(FileTextOutlined, { style: { fontSize: 24 } }) }),
5226
+ contentUrl ? /* @__PURE__ */ jsx(AuthenticatedImage, { url: contentUrl, alt: label, style: imageStyle }) : /* @__PURE__ */ jsx("div", { style: { ...imageStyle, display: "flex", alignItems: "center", justifyContent: "center", color: "#8c8c8c" }, children: /* @__PURE__ */ jsx(FileTextOutlined, { style: { fontSize: 24 } }) }),
5141
5227
  /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: textColor, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: label })
5142
5228
  ]
5143
5229
  },
@@ -6082,6 +6168,8 @@ var AnalysisChart = ({
6082
6168
  };
6083
6169
  const primarySeriesKey = seriesKeys[0] || "__count__";
6084
6170
  const secondarySeriesKey = seriesKeys[1];
6171
+ const resolveNumericField = (fields, n) => fields[Math.min(n, fields.length - 1)] ?? { key: "__count__", label: _10("Count") };
6172
+ const resolveCategoryField = (field1, field2) => field2 ?? field1;
6085
6173
  const getNumericValue = (record, key) => {
6086
6174
  if (key === "__count__") return 1;
6087
6175
  const value = Number(record?.[key]);
@@ -6498,11 +6586,11 @@ var AnalysisChart = ({
6498
6586
  ] });
6499
6587
  };
6500
6588
  const renderScatter = (isBubble) => {
6501
- if (numericFields.length < 2) {
6502
- return /* @__PURE__ */ jsx(Empty, { description: "Scatter needs at least two numeric fields." });
6589
+ if (numericFields.length === 0) {
6590
+ return /* @__PURE__ */ jsx(Empty, { description: "Scatter needs at least one numeric field." });
6503
6591
  }
6504
- const xField = numericFields[0];
6505
- const yField = numericFields[1];
6592
+ const xField = resolveNumericField(numericFields, 0);
6593
+ const yField = resolveNumericField(numericFields, 1);
6506
6594
  const points = rawRows.map((row) => {
6507
6595
  const x = getNumericValue(row, xField.key);
6508
6596
  const y = getNumericValue(row, yField.key);
@@ -6780,11 +6868,12 @@ var AnalysisChart = ({
6780
6868
  ] });
6781
6869
  };
6782
6870
  const renderHeatmap = () => {
6783
- if (!categoryField1 || !categoryField2) {
6784
- return /* @__PURE__ */ jsx(Empty, { description: "Heatmap needs two category fields." });
6871
+ if (!categoryField1) {
6872
+ return /* @__PURE__ */ jsx(Empty, { description: "Heatmap needs a category field." });
6785
6873
  }
6874
+ const effectiveCat2 = resolveCategoryField(categoryField1, categoryField2);
6786
6875
  const cat1Field = modelField(categoryField1);
6787
- const cat2Field = modelField(categoryField2);
6876
+ const cat2Field = modelField(effectiveCat2);
6788
6877
  const rowLabels = [];
6789
6878
  const colLabels = [];
6790
6879
  const grid = /* @__PURE__ */ new Map();
@@ -6862,43 +6951,45 @@ var AnalysisChart = ({
6862
6951
  ] });
6863
6952
  };
6864
6953
  const renderCrosstab = () => {
6865
- if (!categoryField1 || !categoryField2) {
6866
- return /* @__PURE__ */ jsx(Empty, { description: "Crosstab needs two category fields." });
6954
+ if (!categoryField1) {
6955
+ return /* @__PURE__ */ jsx(Empty, { description: "Crosstab needs a category field." });
6867
6956
  }
6957
+ const effectiveCat2 = resolveCategoryField(categoryField1, categoryField2);
6868
6958
  const cat1Field = modelField(categoryField1);
6869
- const cat2Field = modelField(categoryField2);
6959
+ const cat2Field = modelField(effectiveCat2);
6870
6960
  return /* @__PURE__ */ jsx(
6871
6961
  CrosstabTable,
6872
6962
  {
6873
6963
  rows: rawRows,
6874
6964
  rowField: categoryField1,
6875
- colField: categoryField2,
6965
+ colField: effectiveCat2 ?? categoryField1,
6876
6966
  cellFieldKeys: seriesKeys,
6877
6967
  cellFieldLabels: seriesLabels,
6878
6968
  allFields,
6879
6969
  summaryFn,
6880
6970
  formatCategoryValue,
6881
6971
  numericBarColor,
6882
- caption: `${_10("Crosstab")}: ${cat1Field?.label || categoryField1} \xD7 ${cat2Field?.label || categoryField2} (${summaryFn})`
6972
+ caption: `${_10("Crosstab")}: ${cat1Field?.label || categoryField1} \xD7 ${cat2Field?.label || effectiveCat2} (${summaryFn})`
6883
6973
  }
6884
6974
  );
6885
6975
  };
6886
6976
  const renderRadar = () => {
6887
- if (seriesKeys.length < 3) {
6888
- return /* @__PURE__ */ jsx(Empty, { description: "Radar needs at least three series." });
6977
+ if (seriesKeys.length === 0) {
6978
+ return /* @__PURE__ */ jsx(Empty, { description: "Radar needs at least one series." });
6889
6979
  }
6980
+ const effectiveSeriesKeys = seriesKeys.length >= 3 ? seriesKeys : Array.from({ length: 3 }, (_43, i) => seriesKeys[i % seriesKeys.length]);
6890
6981
  const centerX = paddingLeft + chartWidth / 2;
6891
6982
  const centerY = paddingTop + chartHeight / 2;
6892
6983
  const radius = Math.min(chartWidth, chartHeight) * 0.35;
6893
- const maxBySeries = seriesKeys.reduce((acc, key) => {
6984
+ const maxBySeries = effectiveSeriesKeys.reduce((acc, key) => {
6894
6985
  acc[key] = Math.max(...data.map((group) => group.values[key] || 0), 1);
6895
6986
  return acc;
6896
6987
  }, {});
6897
- const angleStep = Math.PI * 2 / seriesKeys.length;
6988
+ const angleStep = Math.PI * 2 / effectiveSeriesKeys.length;
6898
6989
  return /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
6899
6990
  renderTitle(),
6900
6991
  renderCaption("Radar chart"),
6901
- seriesKeys.map((seriesKey, index) => {
6992
+ effectiveSeriesKeys.map((seriesKey, index) => {
6902
6993
  const angle = -Math.PI / 2 + index * angleStep;
6903
6994
  const x = centerX + radius * Math.cos(angle);
6904
6995
  const y = centerY + radius * Math.sin(angle);
@@ -6908,7 +6999,7 @@ var AnalysisChart = ({
6908
6999
  ] }, `radar-axis-${seriesKey}`);
6909
7000
  }),
6910
7001
  data.map((group, groupIndex) => {
6911
- const points = seriesKeys.map((seriesKey, index) => {
7002
+ const points = effectiveSeriesKeys.map((seriesKey, index) => {
6912
7003
  const value = group.values[seriesKey] || 0;
6913
7004
  const ratio = value / Math.max(1, maxBySeries[seriesKey]);
6914
7005
  const angle = -Math.PI / 2 + index * angleStep;
@@ -6933,13 +7024,74 @@ var AnalysisChart = ({
6933
7024
  })
6934
7025
  ] });
6935
7026
  };
6936
- const renderCombo = () => {
6937
- if (!secondarySeriesKey) {
6938
- return /* @__PURE__ */ jsx(Empty, { description: "Combo needs at least two series selected." });
7027
+ const render3D = () => {
7028
+ if (numericFields.length === 0) {
7029
+ return /* @__PURE__ */ jsx(Empty, { description: "3D scatter needs at least one numeric field." });
6939
7030
  }
7031
+ const xField = resolveNumericField(numericFields, 0);
7032
+ const yField = resolveNumericField(numericFields, 1);
7033
+ const zField = resolveNumericField(numericFields, 2);
7034
+ const points = rawRows.map((row) => {
7035
+ const x = getNumericValue(row, xField.key);
7036
+ const y = getNumericValue(row, yField.key);
7037
+ const z = getNumericValue(row, zField.key);
7038
+ if (x === null || y === null || z === null) return null;
7039
+ return { x, y, z };
7040
+ }).filter((p) => !!p);
7041
+ if (points.length === 0) return renderNoChartDataMessage();
7042
+ const xs = points.map((p) => p.x);
7043
+ const ys = points.map((p) => p.y);
7044
+ const zs = points.map((p) => p.z);
7045
+ const xMin = Math.min(...xs), xMax = Math.max(...xs);
7046
+ const yMin = Math.min(...ys), yMax = Math.max(...ys);
7047
+ const zMin = Math.min(...zs), zMax = Math.max(...zs);
7048
+ const norm = (v, lo, hi) => hi === lo ? 0.5 : (v - lo) / (hi - lo);
7049
+ const isoScale = Math.min(chartWidth, chartHeight) * 0.38;
7050
+ const cx = paddingLeft + chartWidth * 0.5;
7051
+ const cy = paddingTop + chartHeight * 0.55;
7052
+ const cos30 = Math.cos(Math.PI / 6);
7053
+ const sin30 = Math.sin(Math.PI / 6);
7054
+ const project = (nx, ny, nz) => ({
7055
+ sx: cx + (nx - nz) * cos30 * isoScale,
7056
+ sy: cy - ny * isoScale + (nx + nz) * sin30 * isoScale
7057
+ });
7058
+ const axisEnd = (nx, ny, nz) => project(nx, ny, nz);
7059
+ const origin = project(0, 0, 0);
7060
+ const xTip = axisEnd(1, 0, 0);
7061
+ const yTip = axisEnd(0, 1, 0);
7062
+ const zTip = axisEnd(0, 0, 1);
7063
+ const projected = points.map(
7064
+ (p) => project(norm(p.x, xMin, xMax), norm(p.y, yMin, yMax), norm(p.z, zMin, zMax))
7065
+ );
7066
+ return /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
7067
+ renderTitle(),
7068
+ renderCaption(`3D: ${xField.label} \xD7 ${yField.label} \xD7 ${zField.label}`),
7069
+ /* @__PURE__ */ jsx("line", { x1: origin.sx, y1: origin.sy, x2: xTip.sx, y2: xTip.sy, stroke: colors[0], strokeWidth: 1.5, opacity: 0.6 }),
7070
+ /* @__PURE__ */ jsx("line", { x1: origin.sx, y1: origin.sy, x2: yTip.sx, y2: yTip.sy, stroke: colors[1], strokeWidth: 1.5, opacity: 0.6 }),
7071
+ /* @__PURE__ */ jsx("line", { x1: origin.sx, y1: origin.sy, x2: zTip.sx, y2: zTip.sy, stroke: colors[2], strokeWidth: 1.5, opacity: 0.6 }),
7072
+ /* @__PURE__ */ jsx("text", { x: xTip.sx + 4, y: xTip.sy + 4, fontSize: "11", fill: colors[0], children: xField.label }),
7073
+ /* @__PURE__ */ jsx("text", { x: yTip.sx + 4, y: yTip.sy, fontSize: "11", fill: colors[1], children: yField.label }),
7074
+ /* @__PURE__ */ jsx("text", { x: zTip.sx + 4, y: zTip.sy + 4, fontSize: "11", fill: colors[2], children: zField.label }),
7075
+ projected.map((p, i) => /* @__PURE__ */ jsx(
7076
+ "circle",
7077
+ {
7078
+ className: "chart-item chart-point",
7079
+ style: { "--delay": `${i * 8}ms` },
7080
+ cx: p.sx,
7081
+ cy: p.sy,
7082
+ r: 4,
7083
+ fill: colors[0],
7084
+ opacity: 0.7
7085
+ },
7086
+ `3d-${i}`
7087
+ ))
7088
+ ] });
7089
+ };
7090
+ const renderCombo = () => {
7091
+ const effectiveSecondaryKey = secondarySeriesKey ?? primarySeriesKey;
6940
7092
  const valuesCombo = data.flatMap((group) => [
6941
7093
  group.values[primarySeriesKey] || 0,
6942
- group.values[secondarySeriesKey] || 0
7094
+ group.values[effectiveSecondaryKey] || 0
6943
7095
  ]);
6944
7096
  const maxCombo = Math.max(...valuesCombo, 1);
6945
7097
  const minCombo = Math.min(...valuesCombo, 0);
@@ -6948,7 +7100,7 @@ var AnalysisChart = ({
6948
7100
  const barWidth2 = groupWidth2 * 0.6;
6949
7101
  const points = data.map((group, index) => {
6950
7102
  const x = paddingLeft + index * groupWidth2 + groupWidth2 / 2;
6951
- const y = scaleYCombo(group.values[secondarySeriesKey] || 0);
7103
+ const y = scaleYCombo(group.values[effectiveSecondaryKey] || 0);
6952
7104
  return `${x},${y}`;
6953
7105
  }).join(" ");
6954
7106
  return /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
@@ -6956,7 +7108,7 @@ var AnalysisChart = ({
6956
7108
  renderLegendItems(
6957
7109
  [
6958
7110
  { label: seriesLabels[primarySeriesKey] || primarySeriesKey, color: colors[0] },
6959
- { label: seriesLabels[secondarySeriesKey] || secondarySeriesKey, color: colors[2] }
7111
+ { label: seriesLabels[effectiveSecondaryKey] || effectiveSecondaryKey, color: colors[2] }
6960
7112
  ],
6961
7113
  8
6962
7114
  ),
@@ -7007,13 +7159,14 @@ var AnalysisChart = ({
7007
7159
  chartType === "histogram" && renderHistogram(),
7008
7160
  chartType === "scatter" && renderScatter(false),
7009
7161
  chartType === "bubble" && renderScatter(true),
7010
- chartType === "box" && renderBoxPlot(),
7162
+ (chartType === "box" || chartType === "boxplot") && renderBoxPlot(),
7011
7163
  chartType === "waterfall" && renderWaterfall(),
7012
7164
  chartType === "heatmap" && renderHeatmap(),
7013
7165
  chartType === "crosstab" && renderCrosstab(),
7014
7166
  chartType === "radar" && renderRadar(),
7015
7167
  chartType === "combo" && renderCombo(),
7016
- chartType !== "histogram" && chartType !== "scatter" && chartType !== "bubble" && chartType !== "box" && chartType !== "waterfall" && chartType !== "heatmap" && chartType !== "crosstab" && chartType !== "radar" && chartType !== "combo" && /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
7168
+ chartType === "3d" && render3D(),
7169
+ (chartType === "bar" || chartType === "line" || chartType === "area" || chartType === "stacked" || chartType === "bar-horizontal" || chartType === "stacked-horizontal" || chartType === "area-horizontal") && /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
7017
7170
  renderTitle(),
7018
7171
  renderLegendItems(
7019
7172
  seriesKeys.map((seriesKey, index) => ({
@@ -7460,6 +7613,13 @@ body, table, th, td, input, button, select, textarea, div, span, p, li, ul, ol {
7460
7613
  if (syncHeightTimerRef.current) clearTimeout(syncHeightTimerRef.current);
7461
7614
  };
7462
7615
  }, []);
7616
+ const inlineHtml = useMemo(
7617
+ () => (html || "").replace(
7618
+ /<script\b[^>]*\bsrc=["']?[^"'>]*cdn\.plot\.ly[^"'>]*["']?[^>]*><\/script>/gi,
7619
+ ""
7620
+ ),
7621
+ [html]
7622
+ );
7463
7623
  if (mode === "iframe") {
7464
7624
  return /* @__PURE__ */ jsx(
7465
7625
  "iframe",
@@ -7470,7 +7630,7 @@ body, table, th, td, input, button, select, textarea, div, span, p, li, ul, ol {
7470
7630
  }
7471
7631
  );
7472
7632
  }
7473
- return /* @__PURE__ */ jsx("div", { ref: htmlRef, dangerouslySetInnerHTML: { __html: html || "" }, style });
7633
+ return /* @__PURE__ */ jsx("div", { ref: htmlRef, dangerouslySetInnerHTML: { __html: inlineHtml }, style });
7474
7634
  };
7475
7635
 
7476
7636
  // src/components/DynamicResource/relations/helpers.ts
@@ -7923,7 +8083,7 @@ var renderModelHeading = ({
7923
8083
  paddingLeft: 10,
7924
8084
  paddingRight: 10
7925
8085
  },
7926
- children: /* @__PURE__ */ jsx("div", { style: { minWidth: 0, fontSize: 16, fontWeight: 700, color: tone.solid, padding: "2px 8px" }, children: title })
8086
+ children: /* @__PURE__ */ jsx("div", { style: { minWidth: 0, fontSize: 18, fontWeight: 700, color: tone.solid, padding: "2px 8px" }, children: title })
7927
8087
  }
7928
8088
  );
7929
8089
  };
@@ -8455,7 +8615,7 @@ var useShowActionsPreferences = (model, allModels, record, saveButtonProps, conf
8455
8615
  headerButtons
8456
8616
  };
8457
8617
  };
8458
- var PrimaryShowContext = React5.createContext(null);
8618
+ var PrimaryShowContext = React6.createContext(null);
8459
8619
 
8460
8620
  // src/components/DynamicResource/utils/columnFilters.ts
8461
8621
  var truncateLabel = (s) => s.length > 15 ? s.substring(0, 15) + "\u2026" : s;
@@ -8686,7 +8846,7 @@ function useRoleFilteredModel(model) {
8686
8846
  }, [model, userRoles]);
8687
8847
  }
8688
8848
  var _19 = window._ || ((text) => text);
8689
- var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded }) => {
8849
+ var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded, beforeTabs }) => {
8690
8850
  const model = useRoleFilteredModel(modelProp);
8691
8851
  applyI18nLabelsToModel(model);
8692
8852
  applyI18nLabelsToModels(allModels);
@@ -8748,6 +8908,7 @@ var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded }) => {
8748
8908
  return /* @__PURE__ */ jsxs("div", { className: "jm-tone-scope", style: toneScopeStyle(modelTone), children: [
8749
8909
  /* @__PURE__ */ jsx(ToneSharedStyles, {}),
8750
8910
  !record ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: 32 }, children: /* @__PURE__ */ jsx(Spin, {}) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8911
+ beforeTabs,
8751
8912
  /* @__PURE__ */ jsx(Tabs, { activeKey: activeTabKey, onChange: setActiveTabKey, items: lazyItems, destroyInactiveTabPane: true }),
8752
8913
  /* @__PURE__ */ jsx(
8753
8914
  ShowFooterButtons,
@@ -8775,6 +8936,7 @@ var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded }) => {
8775
8936
  })),
8776
8937
  headerButtons,
8777
8938
  children: [
8939
+ beforeTabs,
8778
8940
  /* @__PURE__ */ jsx(Tabs, { activeKey: activeTabKey, onChange: setActiveTabKey, items: lazyItems, destroyInactiveTabPane: true }),
8779
8941
  /* @__PURE__ */ jsx(
8780
8942
  ShowFooterButtons,
@@ -8798,7 +8960,7 @@ var RelationSelect = ({ field, value, onChange, allModels, multiple, serverSearc
8798
8960
  const resolvedResource = resourceName && allModels ? resolveResourcePath(resourceName, allModels) : resourceName;
8799
8961
  const referencedModel = resourceName ? findModelByName(allModels, resourceName) : void 0;
8800
8962
  const resolvedOptionValue = field.optionValue || referencedModel?.pkField || "eid";
8801
- const [loadAll, setLoadAll] = React5.useState(false);
8963
+ const [loadAll, setLoadAll] = React6.useState(false);
8802
8964
  const pageSize = loadAll ? 999999 : RELATION_SELECT_DEFAULT_PAGE_SIZE;
8803
8965
  const { selectProps, queryResult } = useSelect({
8804
8966
  resource: resolvedResource,
@@ -8815,8 +8977,8 @@ var RelationSelect = ({ field, value, onChange, allModels, multiple, serverSearc
8815
8977
  const loadedCount = filteredOptions?.length ?? 0;
8816
8978
  const isCapped = !loadAll && serverTotal > loadedCount && loadedCount > 0;
8817
8979
  const normalizeSearch = (val) => String(val ?? "").toLowerCase();
8818
- const selectedSet = React5.useMemo(() => new Set(Array.isArray(value) ? value : value !== void 0 && value !== null ? [value] : []), [value]);
8819
- const [searchValue, setSearchValue] = React5.useState("");
8980
+ const selectedSet = React6.useMemo(() => new Set(Array.isArray(value) ? value : value !== void 0 && value !== null ? [value] : []), [value]);
8981
+ const [searchValue, setSearchValue] = React6.useState("");
8820
8982
  return /* @__PURE__ */ jsxs("div", { children: [
8821
8983
  /* @__PURE__ */ jsx(
8822
8984
  Select,
@@ -9752,7 +9914,9 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9752
9914
  min_width: cell.min_width ?? "",
9753
9915
  max_width: cell.max_width ?? "",
9754
9916
  min_height: cell.min_height ?? "",
9755
- max_height: cell.max_height ?? ""
9917
+ max_height: cell.max_height ?? "",
9918
+ chart_url: cell.chart_url ?? "",
9919
+ chart_title: cell.chart_title ?? ""
9756
9920
  });
9757
9921
  }, [cell, tabId, config, form]);
9758
9922
  const handleSave = () => {
@@ -9768,7 +9932,9 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9768
9932
  min_width: values.min_width || null,
9769
9933
  max_width: values.max_width || null,
9770
9934
  min_height: values.min_height || null,
9771
- max_height: values.max_height || null
9935
+ max_height: values.max_height || null,
9936
+ chart_url: values.chart_url || void 0,
9937
+ chart_title: values.chart_title || void 0
9772
9938
  };
9773
9939
  const currentTab = config.tabs.find((t) => t.id === tabId);
9774
9940
  const nameUnchanged = currentTab?.name.trim().toLowerCase() === newTabName.toLowerCase();
@@ -9815,7 +9981,7 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9815
9981
  return /* @__PURE__ */ jsx(
9816
9982
  Drawer,
9817
9983
  {
9818
- title: cell?.source_type !== "model" ? `Configure section: ${cell?.section_name ?? cell?.model ?? ""}` : `Configure cell: ${cell?.model ?? ""}`,
9984
+ title: cell?.source_type === "plotly_chart" ? `Configure chart: ${cell?.chart_title ?? cell?.model ?? ""}` : cell?.source_type !== "model" ? `Configure section: ${cell?.section_name ?? cell?.model ?? ""}` : `Configure cell: ${cell?.model ?? ""}`,
9819
9985
  placement: "right",
9820
9986
  width: 380,
9821
9987
  open,
@@ -9843,6 +10009,11 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9843
10009
  /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "View" }),
9844
10010
  /* @__PURE__ */ jsx(Form.Item, { name: "view_type", label: "View type", children: /* @__PURE__ */ jsx(Select, { options: VIEW_TYPE_OPTIONS }) })
9845
10011
  ] }),
10012
+ cell?.source_type === "plotly_chart" && /* @__PURE__ */ jsxs(Fragment, { children: [
10013
+ /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Chart" }),
10014
+ /* @__PURE__ */ jsx(Form.Item, { name: "chart_title", label: "Chart title", children: /* @__PURE__ */ jsx(Input, { placeholder: "e.g. Confidence by Month" }) }),
10015
+ /* @__PURE__ */ jsx(Form.Item, { name: "chart_url", label: "Chart URL", children: /* @__PURE__ */ jsx(Input, { placeholder: "/api/nl-answers-confidence-by-month-chart" }) })
10016
+ ] }),
9846
10017
  /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Size" }),
9847
10018
  /* @__PURE__ */ jsxs(Space, { wrap: true, children: [
9848
10019
  /* @__PURE__ */ jsx(Form.Item, { name: "min_width", label: "Min width", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsx(Input, { placeholder: "e.g. 320px", style: { width: 130 } }) }),
@@ -10047,6 +10218,13 @@ var SectionsGrid = ({ cells, config, tabId, renderContent, onConfigChange, isCon
10047
10218
  if (!cells.length) return 1;
10048
10219
  return Math.max(...cells.map((c) => c.row)) + 1;
10049
10220
  }, [cells]);
10221
+ const soloRows = useMemo(() => {
10222
+ const counts = /* @__PURE__ */ new Map();
10223
+ for (const c of cells) counts.set(c.row, (counts.get(c.row) ?? 0) + 1);
10224
+ const solo = /* @__PURE__ */ new Set();
10225
+ for (const [row, count] of counts) if (count === 1) solo.add(row);
10226
+ return solo;
10227
+ }, [cells]);
10050
10228
  const visibleCells = maximizedCellId ? cells.filter((c) => c.id === maximizedCellId) : cells;
10051
10229
  const gridStyle = {
10052
10230
  display: "grid",
@@ -10065,7 +10243,7 @@ var SectionsGrid = ({ cells, config, tabId, renderContent, onConfigChange, isCon
10065
10243
  "div",
10066
10244
  {
10067
10245
  style: {
10068
- gridColumn: maximizedCellId ? "1 / -1" : `${cell.col + 1}`,
10246
+ gridColumn: maximizedCellId || soloRows.has(cell.row) ? "1 / -1" : `${cell.col + 1}`,
10069
10247
  gridRow: maximizedCellId ? "1 / -1" : `${cell.row + 1}`
10070
10248
  },
10071
10249
  children: /* @__PURE__ */ jsx(
@@ -10352,7 +10530,6 @@ var SectionCellContent = ({
10352
10530
  borderRadius: 6,
10353
10531
  overflowWrap: "anywhere",
10354
10532
  maxWidth: "100%",
10355
- border: `1px solid ${token.colorBorder}`,
10356
10533
  ...parseInlineStyle(item.html_format)
10357
10534
  };
10358
10535
  const relationLabelStyle = {
@@ -10387,7 +10564,6 @@ var SectionCellContent = ({
10387
10564
  lineHeight: 1.15,
10388
10565
  background: valueBackground,
10389
10566
  borderRadius: 6,
10390
- border: `1px solid ${token.colorBorder}`,
10391
10567
  maxWidth: "100%",
10392
10568
  overflowWrap: "anywhere",
10393
10569
  textAlign: field.type === "number" && !field.reference ? "right" : "left",
@@ -15386,7 +15562,8 @@ var RelatedObjectsTable = ({ rel, record, relatedModel, parentModel, showActions
15386
15562
  { label: _34("Heatmap"), value: "heatmap" },
15387
15563
  { label: _34("Crosstab"), value: "crosstab" },
15388
15564
  { label: _34("Radar"), value: "radar" },
15389
- { label: _34("Combo (Bar + Line)"), value: "combo" }
15565
+ { label: _34("Combo (Bar + Line)"), value: "combo" },
15566
+ { label: _34("3D Scatter"), value: "3d" }
15390
15567
  ]
15391
15568
  }
15392
15569
  )
@@ -16661,7 +16838,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
16661
16838
  if (["true", "1", "t", "yes", "y"].includes(normalized)) value = true;
16662
16839
  if (["false", "0", "f", "no", "n"].includes(normalized)) value = false;
16663
16840
  }
16664
- return [{ field: searchField.key, operator: "eq", value }];
16841
+ return [{ field: searchField.key, operator: "contains", value }];
16665
16842
  }
16666
16843
  });
16667
16844
  const [allRowsData, setAllRowsData] = useState([]);
@@ -19917,7 +20094,8 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
19917
20094
  { label: _36("Heatmap"), value: "heatmap" },
19918
20095
  { label: _36("Crosstab"), value: "crosstab" },
19919
20096
  { label: _36("Radar"), value: "radar" },
19920
- { label: _36("Combo (Bar + Line)"), value: "combo" }
20097
+ { label: _36("Combo (Bar + Line)"), value: "combo" },
20098
+ { label: _36("3D Scatter"), value: "3d" }
19921
20099
  ]
19922
20100
  }
19923
20101
  )
@@ -21077,6 +21255,48 @@ function useDashboardConfig() {
21077
21255
  }, [apiUrl]);
21078
21256
  return { config, enabled, loading, save, reload: load };
21079
21257
  }
21258
+ var PlotlyChartContent = ({ chartUrl, refreshNonce }) => {
21259
+ const [chartHtml, setChartHtml] = useState("");
21260
+ const [loading, setLoading] = useState(true);
21261
+ const [error, setError] = useState("");
21262
+ const fetchChart = useCallback(async () => {
21263
+ setLoading(true);
21264
+ setError("");
21265
+ try {
21266
+ const apiUrl = typeof API_URL3 === "string" ? API_URL3 : "";
21267
+ const fullUrl = chartUrl.startsWith("http") ? chartUrl : `${apiUrl}${chartUrl}`;
21268
+ const sep = fullUrl.includes("?") ? "&" : "?";
21269
+ const lang = (() => {
21270
+ try {
21271
+ return (localStorage.getItem("locale") || navigator.language || "en").split("-")[0].toLowerCase();
21272
+ } catch {
21273
+ return "en";
21274
+ }
21275
+ })();
21276
+ const res = await authenticatedFetch(`${fullUrl}${sep}lang=${encodeURIComponent(lang)}`);
21277
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
21278
+ const data = await res.json();
21279
+ setChartHtml(data.chart_html || "");
21280
+ } catch (e) {
21281
+ setError(e?.message ?? String(e));
21282
+ } finally {
21283
+ setLoading(false);
21284
+ }
21285
+ }, [chartUrl]);
21286
+ useEffect(() => {
21287
+ fetchChart();
21288
+ }, [fetchChart, refreshNonce]);
21289
+ if (loading) {
21290
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", minHeight: 200 }, children: /* @__PURE__ */ jsx(Spin, {}) });
21291
+ }
21292
+ if (error) {
21293
+ return /* @__PURE__ */ jsx(Empty, { description: `Chart error: ${error}`, style: { padding: 20 }, image: Empty.PRESENTED_IMAGE_SIMPLE });
21294
+ }
21295
+ if (!chartHtml) {
21296
+ return /* @__PURE__ */ jsx(Empty, { description: "No chart data", style: { padding: 20 }, image: Empty.PRESENTED_IMAGE_SIMPLE });
21297
+ }
21298
+ return /* @__PURE__ */ jsx(InlinePlotlyHtml, { html: chartHtml, style: { padding: 8, height: "100%", overflow: "auto" } });
21299
+ };
21080
21300
  var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, canConfigureLayout, onConfigure, onMaximize, onMinimize, onResize, onMove }) => {
21081
21301
  const { token } = theme.useToken();
21082
21302
  const model = findModelByName(allModels, cell.model);
@@ -21109,10 +21329,12 @@ var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, canConfigu
21109
21329
  minHeight: 32,
21110
21330
  position: "relative"
21111
21331
  };
21332
+ const isPlotlyChart = cell.source_type === "plotly_chart";
21112
21333
  const resource = model?.resource || cell.model;
21113
21334
  const isModelLike = cell.source_type === "model" || cell.source_type === "named_query";
21114
- const cellTitle = isModelLike ? model?.label || cell.model : cell.section_name || cell.model;
21335
+ const cellTitle = isPlotlyChart ? cell.chart_title || cell.model : isModelLike ? model?.label || cell.model : cell.section_name || cell.model;
21115
21336
  const tone = isModelLike && model ? getModelTone(model) : null;
21337
+ const [chartRefreshNonce, setChartRefreshNonce] = useState(0);
21116
21338
  const startResize = useCallback((e, dir) => {
21117
21339
  e.preventDefault();
21118
21340
  e.stopPropagation();
@@ -21266,7 +21488,7 @@ var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, canConfigu
21266
21488
  ) })
21267
21489
  ] })
21268
21490
  ] }),
21269
- !isMinimized && /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto", minHeight: 0 }, children: model ? /* @__PURE__ */ jsx(
21491
+ !isMinimized && /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto", minHeight: 0 }, children: isPlotlyChart && cell.chart_url ? /* @__PURE__ */ jsx(PlotlyChartContent, { chartUrl: cell.chart_url, refreshNonce: chartRefreshNonce }) : model ? /* @__PURE__ */ jsx(
21270
21492
  DynamicList,
21271
21493
  {
21272
21494
  model,
@@ -21604,7 +21826,7 @@ function usePinnedRecords() {
21604
21826
  setLoading(false);
21605
21827
  }
21606
21828
  }, []);
21607
- React5.useEffect(() => {
21829
+ React6.useEffect(() => {
21608
21830
  load();
21609
21831
  }, [load]);
21610
21832
  return { groups, loading, reload: load };
@@ -21942,6 +22164,6 @@ var authSystemModels = [
21942
22164
  }
21943
22165
  ];
21944
22166
 
21945
- export { API_URL3 as API_URL, AllModelsProvider, ColorModeContext, ColorModeContextProvider, CommandCenterPortal, CustomSider, DashboardPage, DynamicCreate, DynamicEdit, DynamicList, DynamicShow, ExecutableHtml, GlobalSearch, HierarchyView, HorizontalMenu, InlinePlotlyHtml, LayoutWrapper, LoginPage, ModelHeading, MultiPaneLayout, NavConfigContext, PaneNavigationContext, PinnedRecordsPanel, PrimaryShowContext, RecentActivityPanel, ReferenceField, ResourceContext, SectionsGrid, ShowFooterButtons, StandardList, StandardShow, ViewsGrid, accessControlProvider, authProvider, authSystemModels, authenticatedFetch, buildShowTabFormOptions, generateResources, getModelTone, getNavEntry, guessIcon, httpClient, normalizeToneKey, renderRelationBlock, resolveIcon, setColorSchemas, sortItemsByNavConfig, useAllModels, useKeyboardShortcuts, useMetadataModal, useNavConfig, useNavModules, usePaneNavigation, useRecordSearch, useShowActionsPreferences, useShowEditableForm, useStandardShowTabs };
22167
+ export { API_URL3 as API_URL, AllModelsProvider, AuthenticatedImage, ColorModeContext, ColorModeContextProvider, CommandCenterPortal, CustomSider, DashboardPage, DynamicCreate, DynamicEdit, DynamicList, DynamicShow, ExecutableHtml, GlobalSearch, HierarchyView, HorizontalMenu, InlinePlotlyHtml, LayoutWrapper, LoginPage, ModelHeading, MultiPaneLayout, NavConfigContext, PaneNavigationContext, PinnedRecordsPanel, PrimaryShowContext, RecentActivityPanel, ReferenceField, ResourceContext, SectionsGrid, ShowFooterButtons, StandardList, StandardShow, ViewsGrid, accessControlProvider, authProvider, authSystemModels, authenticatedFetch, buildShowTabFormOptions, generateResources, getModelTone, getNavEntry, guessIcon, httpClient, normalizeToneKey, renderRelationBlock, resolveIcon, setColorSchemas, sortItemsByNavConfig, useAllModels, useAuthenticatedFileUrl, useKeyboardShortcuts, useMetadataModal, useNavConfig, useNavModules, usePaneNavigation, useRecordSearch, useShowActionsPreferences, useShowEditableForm, useStandardShowTabs };
21946
22168
  //# sourceMappingURL=index.mjs.map
21947
22169
  //# sourceMappingURL=index.mjs.map