@juicemantics/veloiq-ui 0.8.4 → 0.9.0

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
@@ -5080,6 +5080,39 @@ var getSortPriority = (columnSort, fieldKey) => {
5080
5080
  const index = columnSort.findIndex((item) => item.fieldKey === fieldKey);
5081
5081
  return index === -1 ? 1 : columnSort.length - index + 1;
5082
5082
  };
5083
+ var _TOKEN_KEY = "jm_access_token";
5084
+ function useAuthenticatedFileUrl(rawUrl) {
5085
+ const [src, setSrc] = useState("");
5086
+ useEffect(() => {
5087
+ if (!rawUrl) {
5088
+ setSrc("");
5089
+ return;
5090
+ }
5091
+ if (!rawUrl.includes("/api/file/")) {
5092
+ setSrc(rawUrl);
5093
+ return;
5094
+ }
5095
+ const token = localStorage.getItem(_TOKEN_KEY) || "";
5096
+ const controller = new AbortController();
5097
+ let objectUrl = "";
5098
+ fetch(rawUrl, {
5099
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
5100
+ signal: controller.signal
5101
+ }).then((r) => r.ok ? r.blob() : Promise.reject()).then((blob) => {
5102
+ objectUrl = URL.createObjectURL(blob);
5103
+ setSrc(objectUrl);
5104
+ }).catch(() => setSrc(""));
5105
+ return () => {
5106
+ controller.abort();
5107
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
5108
+ };
5109
+ }, [rawUrl]);
5110
+ return src;
5111
+ }
5112
+ var AuthenticatedImage = ({ url, alt, style }) => {
5113
+ const src = useAuthenticatedFileUrl(url);
5114
+ return src ? /* @__PURE__ */ jsx("img", { src, alt: alt ?? "", style }) : null;
5115
+ };
5083
5116
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "tif", "tiff"]);
5084
5117
  var isImageRecord = (record) => {
5085
5118
  if (record?.avatar_url || record?.image_url || record?.photo_url) return true;
@@ -5137,7 +5170,7 @@ var renderSharedGalleryCard = ({
5137
5170
  style: { width: imageWidth, display: "grid", gap: 6, cursor: onClick ? "pointer" : "default" },
5138
5171
  onClick,
5139
5172
  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 } }) }),
5173
+ 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
5174
  /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: textColor, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: label })
5142
5175
  ]
5143
5176
  },
@@ -6082,6 +6115,8 @@ var AnalysisChart = ({
6082
6115
  };
6083
6116
  const primarySeriesKey = seriesKeys[0] || "__count__";
6084
6117
  const secondarySeriesKey = seriesKeys[1];
6118
+ const resolveNumericField = (fields, n) => fields[Math.min(n, fields.length - 1)] ?? { key: "__count__", label: _10("Count") };
6119
+ const resolveCategoryField = (field1, field2) => field2 ?? field1;
6085
6120
  const getNumericValue = (record, key) => {
6086
6121
  if (key === "__count__") return 1;
6087
6122
  const value = Number(record?.[key]);
@@ -6498,11 +6533,11 @@ var AnalysisChart = ({
6498
6533
  ] });
6499
6534
  };
6500
6535
  const renderScatter = (isBubble) => {
6501
- if (numericFields.length < 2) {
6502
- return /* @__PURE__ */ jsx(Empty, { description: "Scatter needs at least two numeric fields." });
6536
+ if (numericFields.length === 0) {
6537
+ return /* @__PURE__ */ jsx(Empty, { description: "Scatter needs at least one numeric field." });
6503
6538
  }
6504
- const xField = numericFields[0];
6505
- const yField = numericFields[1];
6539
+ const xField = resolveNumericField(numericFields, 0);
6540
+ const yField = resolveNumericField(numericFields, 1);
6506
6541
  const points = rawRows.map((row) => {
6507
6542
  const x = getNumericValue(row, xField.key);
6508
6543
  const y = getNumericValue(row, yField.key);
@@ -6780,11 +6815,12 @@ var AnalysisChart = ({
6780
6815
  ] });
6781
6816
  };
6782
6817
  const renderHeatmap = () => {
6783
- if (!categoryField1 || !categoryField2) {
6784
- return /* @__PURE__ */ jsx(Empty, { description: "Heatmap needs two category fields." });
6818
+ if (!categoryField1) {
6819
+ return /* @__PURE__ */ jsx(Empty, { description: "Heatmap needs a category field." });
6785
6820
  }
6821
+ const effectiveCat2 = resolveCategoryField(categoryField1, categoryField2);
6786
6822
  const cat1Field = modelField(categoryField1);
6787
- const cat2Field = modelField(categoryField2);
6823
+ const cat2Field = modelField(effectiveCat2);
6788
6824
  const rowLabels = [];
6789
6825
  const colLabels = [];
6790
6826
  const grid = /* @__PURE__ */ new Map();
@@ -6862,43 +6898,45 @@ var AnalysisChart = ({
6862
6898
  ] });
6863
6899
  };
6864
6900
  const renderCrosstab = () => {
6865
- if (!categoryField1 || !categoryField2) {
6866
- return /* @__PURE__ */ jsx(Empty, { description: "Crosstab needs two category fields." });
6901
+ if (!categoryField1) {
6902
+ return /* @__PURE__ */ jsx(Empty, { description: "Crosstab needs a category field." });
6867
6903
  }
6904
+ const effectiveCat2 = resolveCategoryField(categoryField1, categoryField2);
6868
6905
  const cat1Field = modelField(categoryField1);
6869
- const cat2Field = modelField(categoryField2);
6906
+ const cat2Field = modelField(effectiveCat2);
6870
6907
  return /* @__PURE__ */ jsx(
6871
6908
  CrosstabTable,
6872
6909
  {
6873
6910
  rows: rawRows,
6874
6911
  rowField: categoryField1,
6875
- colField: categoryField2,
6912
+ colField: effectiveCat2 ?? categoryField1,
6876
6913
  cellFieldKeys: seriesKeys,
6877
6914
  cellFieldLabels: seriesLabels,
6878
6915
  allFields,
6879
6916
  summaryFn,
6880
6917
  formatCategoryValue,
6881
6918
  numericBarColor,
6882
- caption: `${_10("Crosstab")}: ${cat1Field?.label || categoryField1} \xD7 ${cat2Field?.label || categoryField2} (${summaryFn})`
6919
+ caption: `${_10("Crosstab")}: ${cat1Field?.label || categoryField1} \xD7 ${cat2Field?.label || effectiveCat2} (${summaryFn})`
6883
6920
  }
6884
6921
  );
6885
6922
  };
6886
6923
  const renderRadar = () => {
6887
- if (seriesKeys.length < 3) {
6888
- return /* @__PURE__ */ jsx(Empty, { description: "Radar needs at least three series." });
6924
+ if (seriesKeys.length === 0) {
6925
+ return /* @__PURE__ */ jsx(Empty, { description: "Radar needs at least one series." });
6889
6926
  }
6927
+ const effectiveSeriesKeys = seriesKeys.length >= 3 ? seriesKeys : Array.from({ length: 3 }, (_43, i) => seriesKeys[i % seriesKeys.length]);
6890
6928
  const centerX = paddingLeft + chartWidth / 2;
6891
6929
  const centerY = paddingTop + chartHeight / 2;
6892
6930
  const radius = Math.min(chartWidth, chartHeight) * 0.35;
6893
- const maxBySeries = seriesKeys.reduce((acc, key) => {
6931
+ const maxBySeries = effectiveSeriesKeys.reduce((acc, key) => {
6894
6932
  acc[key] = Math.max(...data.map((group) => group.values[key] || 0), 1);
6895
6933
  return acc;
6896
6934
  }, {});
6897
- const angleStep = Math.PI * 2 / seriesKeys.length;
6935
+ const angleStep = Math.PI * 2 / effectiveSeriesKeys.length;
6898
6936
  return /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
6899
6937
  renderTitle(),
6900
6938
  renderCaption("Radar chart"),
6901
- seriesKeys.map((seriesKey, index) => {
6939
+ effectiveSeriesKeys.map((seriesKey, index) => {
6902
6940
  const angle = -Math.PI / 2 + index * angleStep;
6903
6941
  const x = centerX + radius * Math.cos(angle);
6904
6942
  const y = centerY + radius * Math.sin(angle);
@@ -6908,7 +6946,7 @@ var AnalysisChart = ({
6908
6946
  ] }, `radar-axis-${seriesKey}`);
6909
6947
  }),
6910
6948
  data.map((group, groupIndex) => {
6911
- const points = seriesKeys.map((seriesKey, index) => {
6949
+ const points = effectiveSeriesKeys.map((seriesKey, index) => {
6912
6950
  const value = group.values[seriesKey] || 0;
6913
6951
  const ratio = value / Math.max(1, maxBySeries[seriesKey]);
6914
6952
  const angle = -Math.PI / 2 + index * angleStep;
@@ -6933,13 +6971,74 @@ var AnalysisChart = ({
6933
6971
  })
6934
6972
  ] });
6935
6973
  };
6936
- const renderCombo = () => {
6937
- if (!secondarySeriesKey) {
6938
- return /* @__PURE__ */ jsx(Empty, { description: "Combo needs at least two series selected." });
6974
+ const render3D = () => {
6975
+ if (numericFields.length === 0) {
6976
+ return /* @__PURE__ */ jsx(Empty, { description: "3D scatter needs at least one numeric field." });
6939
6977
  }
6978
+ const xField = resolveNumericField(numericFields, 0);
6979
+ const yField = resolveNumericField(numericFields, 1);
6980
+ const zField = resolveNumericField(numericFields, 2);
6981
+ const points = rawRows.map((row) => {
6982
+ const x = getNumericValue(row, xField.key);
6983
+ const y = getNumericValue(row, yField.key);
6984
+ const z = getNumericValue(row, zField.key);
6985
+ if (x === null || y === null || z === null) return null;
6986
+ return { x, y, z };
6987
+ }).filter((p) => !!p);
6988
+ if (points.length === 0) return renderNoChartDataMessage();
6989
+ const xs = points.map((p) => p.x);
6990
+ const ys = points.map((p) => p.y);
6991
+ const zs = points.map((p) => p.z);
6992
+ const xMin = Math.min(...xs), xMax = Math.max(...xs);
6993
+ const yMin = Math.min(...ys), yMax = Math.max(...ys);
6994
+ const zMin = Math.min(...zs), zMax = Math.max(...zs);
6995
+ const norm = (v, lo, hi) => hi === lo ? 0.5 : (v - lo) / (hi - lo);
6996
+ const isoScale = Math.min(chartWidth, chartHeight) * 0.38;
6997
+ const cx = paddingLeft + chartWidth * 0.5;
6998
+ const cy = paddingTop + chartHeight * 0.55;
6999
+ const cos30 = Math.cos(Math.PI / 6);
7000
+ const sin30 = Math.sin(Math.PI / 6);
7001
+ const project = (nx, ny, nz) => ({
7002
+ sx: cx + (nx - nz) * cos30 * isoScale,
7003
+ sy: cy - ny * isoScale + (nx + nz) * sin30 * isoScale
7004
+ });
7005
+ const axisEnd = (nx, ny, nz) => project(nx, ny, nz);
7006
+ const origin = project(0, 0, 0);
7007
+ const xTip = axisEnd(1, 0, 0);
7008
+ const yTip = axisEnd(0, 1, 0);
7009
+ const zTip = axisEnd(0, 0, 1);
7010
+ const projected = points.map(
7011
+ (p) => project(norm(p.x, xMin, xMax), norm(p.y, yMin, yMax), norm(p.z, zMin, zMax))
7012
+ );
7013
+ return /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
7014
+ renderTitle(),
7015
+ renderCaption(`3D: ${xField.label} \xD7 ${yField.label} \xD7 ${zField.label}`),
7016
+ /* @__PURE__ */ jsx("line", { x1: origin.sx, y1: origin.sy, x2: xTip.sx, y2: xTip.sy, stroke: colors[0], strokeWidth: 1.5, opacity: 0.6 }),
7017
+ /* @__PURE__ */ jsx("line", { x1: origin.sx, y1: origin.sy, x2: yTip.sx, y2: yTip.sy, stroke: colors[1], strokeWidth: 1.5, opacity: 0.6 }),
7018
+ /* @__PURE__ */ jsx("line", { x1: origin.sx, y1: origin.sy, x2: zTip.sx, y2: zTip.sy, stroke: colors[2], strokeWidth: 1.5, opacity: 0.6 }),
7019
+ /* @__PURE__ */ jsx("text", { x: xTip.sx + 4, y: xTip.sy + 4, fontSize: "11", fill: colors[0], children: xField.label }),
7020
+ /* @__PURE__ */ jsx("text", { x: yTip.sx + 4, y: yTip.sy, fontSize: "11", fill: colors[1], children: yField.label }),
7021
+ /* @__PURE__ */ jsx("text", { x: zTip.sx + 4, y: zTip.sy + 4, fontSize: "11", fill: colors[2], children: zField.label }),
7022
+ projected.map((p, i) => /* @__PURE__ */ jsx(
7023
+ "circle",
7024
+ {
7025
+ className: "chart-item chart-point",
7026
+ style: { "--delay": `${i * 8}ms` },
7027
+ cx: p.sx,
7028
+ cy: p.sy,
7029
+ r: 4,
7030
+ fill: colors[0],
7031
+ opacity: 0.7
7032
+ },
7033
+ `3d-${i}`
7034
+ ))
7035
+ ] });
7036
+ };
7037
+ const renderCombo = () => {
7038
+ const effectiveSecondaryKey = secondarySeriesKey ?? primarySeriesKey;
6940
7039
  const valuesCombo = data.flatMap((group) => [
6941
7040
  group.values[primarySeriesKey] || 0,
6942
- group.values[secondarySeriesKey] || 0
7041
+ group.values[effectiveSecondaryKey] || 0
6943
7042
  ]);
6944
7043
  const maxCombo = Math.max(...valuesCombo, 1);
6945
7044
  const minCombo = Math.min(...valuesCombo, 0);
@@ -6948,7 +7047,7 @@ var AnalysisChart = ({
6948
7047
  const barWidth2 = groupWidth2 * 0.6;
6949
7048
  const points = data.map((group, index) => {
6950
7049
  const x = paddingLeft + index * groupWidth2 + groupWidth2 / 2;
6951
- const y = scaleYCombo(group.values[secondarySeriesKey] || 0);
7050
+ const y = scaleYCombo(group.values[effectiveSecondaryKey] || 0);
6952
7051
  return `${x},${y}`;
6953
7052
  }).join(" ");
6954
7053
  return /* @__PURE__ */ jsxs("svg", { ref: svgRef, className: "chart-plot", viewBox: `0 0 ${width} ${height}`, width: "100%", height, role: "img", children: [
@@ -6956,7 +7055,7 @@ var AnalysisChart = ({
6956
7055
  renderLegendItems(
6957
7056
  [
6958
7057
  { label: seriesLabels[primarySeriesKey] || primarySeriesKey, color: colors[0] },
6959
- { label: seriesLabels[secondarySeriesKey] || secondarySeriesKey, color: colors[2] }
7058
+ { label: seriesLabels[effectiveSecondaryKey] || effectiveSecondaryKey, color: colors[2] }
6960
7059
  ],
6961
7060
  8
6962
7061
  ),
@@ -7007,13 +7106,14 @@ var AnalysisChart = ({
7007
7106
  chartType === "histogram" && renderHistogram(),
7008
7107
  chartType === "scatter" && renderScatter(false),
7009
7108
  chartType === "bubble" && renderScatter(true),
7010
- chartType === "box" && renderBoxPlot(),
7109
+ (chartType === "box" || chartType === "boxplot") && renderBoxPlot(),
7011
7110
  chartType === "waterfall" && renderWaterfall(),
7012
7111
  chartType === "heatmap" && renderHeatmap(),
7013
7112
  chartType === "crosstab" && renderCrosstab(),
7014
7113
  chartType === "radar" && renderRadar(),
7015
7114
  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: [
7115
+ chartType === "3d" && render3D(),
7116
+ (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
7117
  renderTitle(),
7018
7118
  renderLegendItems(
7019
7119
  seriesKeys.map((seriesKey, index) => ({
@@ -7460,6 +7560,13 @@ body, table, th, td, input, button, select, textarea, div, span, p, li, ul, ol {
7460
7560
  if (syncHeightTimerRef.current) clearTimeout(syncHeightTimerRef.current);
7461
7561
  };
7462
7562
  }, []);
7563
+ const inlineHtml = useMemo(
7564
+ () => (html || "").replace(
7565
+ /<script\b[^>]*\bsrc=["']?[^"'>]*cdn\.plot\.ly[^"'>]*["']?[^>]*><\/script>/gi,
7566
+ ""
7567
+ ),
7568
+ [html]
7569
+ );
7463
7570
  if (mode === "iframe") {
7464
7571
  return /* @__PURE__ */ jsx(
7465
7572
  "iframe",
@@ -7470,7 +7577,7 @@ body, table, th, td, input, button, select, textarea, div, span, p, li, ul, ol {
7470
7577
  }
7471
7578
  );
7472
7579
  }
7473
- return /* @__PURE__ */ jsx("div", { ref: htmlRef, dangerouslySetInnerHTML: { __html: html || "" }, style });
7580
+ return /* @__PURE__ */ jsx("div", { ref: htmlRef, dangerouslySetInnerHTML: { __html: inlineHtml }, style });
7474
7581
  };
7475
7582
 
7476
7583
  // src/components/DynamicResource/relations/helpers.ts
@@ -8686,7 +8793,7 @@ function useRoleFilteredModel(model) {
8686
8793
  }, [model, userRoles]);
8687
8794
  }
8688
8795
  var _19 = window._ || ((text) => text);
8689
- var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded }) => {
8796
+ var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded, beforeTabs }) => {
8690
8797
  const model = useRoleFilteredModel(modelProp);
8691
8798
  applyI18nLabelsToModel(model);
8692
8799
  applyI18nLabelsToModels(allModels);
@@ -8748,6 +8855,7 @@ var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded }) => {
8748
8855
  return /* @__PURE__ */ jsxs("div", { className: "jm-tone-scope", style: toneScopeStyle(modelTone), children: [
8749
8856
  /* @__PURE__ */ jsx(ToneSharedStyles, {}),
8750
8857
  !record ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: 32 }, children: /* @__PURE__ */ jsx(Spin, {}) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
8858
+ beforeTabs,
8751
8859
  /* @__PURE__ */ jsx(Tabs, { activeKey: activeTabKey, onChange: setActiveTabKey, items: lazyItems, destroyInactiveTabPane: true }),
8752
8860
  /* @__PURE__ */ jsx(
8753
8861
  ShowFooterButtons,
@@ -8775,6 +8883,7 @@ var DynamicShow = ({ model: modelProp, allModels, idOverride, embedded }) => {
8775
8883
  })),
8776
8884
  headerButtons,
8777
8885
  children: [
8886
+ beforeTabs,
8778
8887
  /* @__PURE__ */ jsx(Tabs, { activeKey: activeTabKey, onChange: setActiveTabKey, items: lazyItems, destroyInactiveTabPane: true }),
8779
8888
  /* @__PURE__ */ jsx(
8780
8889
  ShowFooterButtons,
@@ -9752,7 +9861,9 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9752
9861
  min_width: cell.min_width ?? "",
9753
9862
  max_width: cell.max_width ?? "",
9754
9863
  min_height: cell.min_height ?? "",
9755
- max_height: cell.max_height ?? ""
9864
+ max_height: cell.max_height ?? "",
9865
+ chart_url: cell.chart_url ?? "",
9866
+ chart_title: cell.chart_title ?? ""
9756
9867
  });
9757
9868
  }, [cell, tabId, config, form]);
9758
9869
  const handleSave = () => {
@@ -9768,7 +9879,9 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9768
9879
  min_width: values.min_width || null,
9769
9880
  max_width: values.max_width || null,
9770
9881
  min_height: values.min_height || null,
9771
- max_height: values.max_height || null
9882
+ max_height: values.max_height || null,
9883
+ chart_url: values.chart_url || void 0,
9884
+ chart_title: values.chart_title || void 0
9772
9885
  };
9773
9886
  const currentTab = config.tabs.find((t) => t.id === tabId);
9774
9887
  const nameUnchanged = currentTab?.name.trim().toLowerCase() === newTabName.toLowerCase();
@@ -9815,7 +9928,7 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9815
9928
  return /* @__PURE__ */ jsx(
9816
9929
  Drawer,
9817
9930
  {
9818
- title: cell?.source_type !== "model" ? `Configure section: ${cell?.section_name ?? cell?.model ?? ""}` : `Configure cell: ${cell?.model ?? ""}`,
9931
+ 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
9932
  placement: "right",
9820
9933
  width: 380,
9821
9934
  open,
@@ -9843,6 +9956,11 @@ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
9843
9956
  /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "View" }),
9844
9957
  /* @__PURE__ */ jsx(Form.Item, { name: "view_type", label: "View type", children: /* @__PURE__ */ jsx(Select, { options: VIEW_TYPE_OPTIONS }) })
9845
9958
  ] }),
9959
+ cell?.source_type === "plotly_chart" && /* @__PURE__ */ jsxs(Fragment, { children: [
9960
+ /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Chart" }),
9961
+ /* @__PURE__ */ jsx(Form.Item, { name: "chart_title", label: "Chart title", children: /* @__PURE__ */ jsx(Input, { placeholder: "e.g. Confidence by Month" }) }),
9962
+ /* @__PURE__ */ jsx(Form.Item, { name: "chart_url", label: "Chart URL", children: /* @__PURE__ */ jsx(Input, { placeholder: "/api/nl-answers-confidence-by-month-chart" }) })
9963
+ ] }),
9846
9964
  /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Size" }),
9847
9965
  /* @__PURE__ */ jsxs(Space, { wrap: true, children: [
9848
9966
  /* @__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 +10165,13 @@ var SectionsGrid = ({ cells, config, tabId, renderContent, onConfigChange, isCon
10047
10165
  if (!cells.length) return 1;
10048
10166
  return Math.max(...cells.map((c) => c.row)) + 1;
10049
10167
  }, [cells]);
10168
+ const soloRows = useMemo(() => {
10169
+ const counts = /* @__PURE__ */ new Map();
10170
+ for (const c of cells) counts.set(c.row, (counts.get(c.row) ?? 0) + 1);
10171
+ const solo = /* @__PURE__ */ new Set();
10172
+ for (const [row, count] of counts) if (count === 1) solo.add(row);
10173
+ return solo;
10174
+ }, [cells]);
10050
10175
  const visibleCells = maximizedCellId ? cells.filter((c) => c.id === maximizedCellId) : cells;
10051
10176
  const gridStyle = {
10052
10177
  display: "grid",
@@ -10065,7 +10190,7 @@ var SectionsGrid = ({ cells, config, tabId, renderContent, onConfigChange, isCon
10065
10190
  "div",
10066
10191
  {
10067
10192
  style: {
10068
- gridColumn: maximizedCellId ? "1 / -1" : `${cell.col + 1}`,
10193
+ gridColumn: maximizedCellId || soloRows.has(cell.row) ? "1 / -1" : `${cell.col + 1}`,
10069
10194
  gridRow: maximizedCellId ? "1 / -1" : `${cell.row + 1}`
10070
10195
  },
10071
10196
  children: /* @__PURE__ */ jsx(
@@ -15386,7 +15511,8 @@ var RelatedObjectsTable = ({ rel, record, relatedModel, parentModel, showActions
15386
15511
  { label: _34("Heatmap"), value: "heatmap" },
15387
15512
  { label: _34("Crosstab"), value: "crosstab" },
15388
15513
  { label: _34("Radar"), value: "radar" },
15389
- { label: _34("Combo (Bar + Line)"), value: "combo" }
15514
+ { label: _34("Combo (Bar + Line)"), value: "combo" },
15515
+ { label: _34("3D Scatter"), value: "3d" }
15390
15516
  ]
15391
15517
  }
15392
15518
  )
@@ -16661,7 +16787,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
16661
16787
  if (["true", "1", "t", "yes", "y"].includes(normalized)) value = true;
16662
16788
  if (["false", "0", "f", "no", "n"].includes(normalized)) value = false;
16663
16789
  }
16664
- return [{ field: searchField.key, operator: "eq", value }];
16790
+ return [{ field: searchField.key, operator: "contains", value }];
16665
16791
  }
16666
16792
  });
16667
16793
  const [allRowsData, setAllRowsData] = useState([]);
@@ -19917,7 +20043,8 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
19917
20043
  { label: _36("Heatmap"), value: "heatmap" },
19918
20044
  { label: _36("Crosstab"), value: "crosstab" },
19919
20045
  { label: _36("Radar"), value: "radar" },
19920
- { label: _36("Combo (Bar + Line)"), value: "combo" }
20046
+ { label: _36("Combo (Bar + Line)"), value: "combo" },
20047
+ { label: _36("3D Scatter"), value: "3d" }
19921
20048
  ]
19922
20049
  }
19923
20050
  )
@@ -21077,6 +21204,48 @@ function useDashboardConfig() {
21077
21204
  }, [apiUrl]);
21078
21205
  return { config, enabled, loading, save, reload: load };
21079
21206
  }
21207
+ var PlotlyChartContent = ({ chartUrl, refreshNonce }) => {
21208
+ const [chartHtml, setChartHtml] = useState("");
21209
+ const [loading, setLoading] = useState(true);
21210
+ const [error, setError] = useState("");
21211
+ const fetchChart = useCallback(async () => {
21212
+ setLoading(true);
21213
+ setError("");
21214
+ try {
21215
+ const apiUrl = typeof API_URL3 === "string" ? API_URL3 : "";
21216
+ const fullUrl = chartUrl.startsWith("http") ? chartUrl : `${apiUrl}${chartUrl}`;
21217
+ const sep = fullUrl.includes("?") ? "&" : "?";
21218
+ const lang = (() => {
21219
+ try {
21220
+ return (localStorage.getItem("locale") || navigator.language || "en").split("-")[0].toLowerCase();
21221
+ } catch {
21222
+ return "en";
21223
+ }
21224
+ })();
21225
+ const res = await authenticatedFetch(`${fullUrl}${sep}lang=${encodeURIComponent(lang)}`);
21226
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
21227
+ const data = await res.json();
21228
+ setChartHtml(data.chart_html || "");
21229
+ } catch (e) {
21230
+ setError(e?.message ?? String(e));
21231
+ } finally {
21232
+ setLoading(false);
21233
+ }
21234
+ }, [chartUrl]);
21235
+ useEffect(() => {
21236
+ fetchChart();
21237
+ }, [fetchChart, refreshNonce]);
21238
+ if (loading) {
21239
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", minHeight: 200 }, children: /* @__PURE__ */ jsx(Spin, {}) });
21240
+ }
21241
+ if (error) {
21242
+ return /* @__PURE__ */ jsx(Empty, { description: `Chart error: ${error}`, style: { padding: 20 }, image: Empty.PRESENTED_IMAGE_SIMPLE });
21243
+ }
21244
+ if (!chartHtml) {
21245
+ return /* @__PURE__ */ jsx(Empty, { description: "No chart data", style: { padding: 20 }, image: Empty.PRESENTED_IMAGE_SIMPLE });
21246
+ }
21247
+ return /* @__PURE__ */ jsx(InlinePlotlyHtml, { html: chartHtml, style: { padding: 8, height: "100%", overflow: "auto" } });
21248
+ };
21080
21249
  var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, canConfigureLayout, onConfigure, onMaximize, onMinimize, onResize, onMove }) => {
21081
21250
  const { token } = theme.useToken();
21082
21251
  const model = findModelByName(allModels, cell.model);
@@ -21109,10 +21278,12 @@ var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, canConfigu
21109
21278
  minHeight: 32,
21110
21279
  position: "relative"
21111
21280
  };
21281
+ const isPlotlyChart = cell.source_type === "plotly_chart";
21112
21282
  const resource = model?.resource || cell.model;
21113
21283
  const isModelLike = cell.source_type === "model" || cell.source_type === "named_query";
21114
- const cellTitle = isModelLike ? model?.label || cell.model : cell.section_name || cell.model;
21284
+ const cellTitle = isPlotlyChart ? cell.chart_title || cell.model : isModelLike ? model?.label || cell.model : cell.section_name || cell.model;
21115
21285
  const tone = isModelLike && model ? getModelTone(model) : null;
21286
+ const [chartRefreshNonce, setChartRefreshNonce] = useState(0);
21116
21287
  const startResize = useCallback((e, dir) => {
21117
21288
  e.preventDefault();
21118
21289
  e.stopPropagation();
@@ -21266,7 +21437,7 @@ var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, canConfigu
21266
21437
  ) })
21267
21438
  ] })
21268
21439
  ] }),
21269
- !isMinimized && /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto", minHeight: 0 }, children: model ? /* @__PURE__ */ jsx(
21440
+ !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
21441
  DynamicList,
21271
21442
  {
21272
21443
  model,
@@ -21942,6 +22113,6 @@ var authSystemModels = [
21942
22113
  }
21943
22114
  ];
21944
22115
 
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 };
22116
+ 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
22117
  //# sourceMappingURL=index.mjs.map
21947
22118
  //# sourceMappingURL=index.mjs.map