@juicemantics/veloiq-ui 0.1.0 → 0.2.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.js CHANGED
@@ -866,7 +866,7 @@ var LayoutWrapper = ({
866
866
  label: "Confirm Password",
867
867
  dependencies: ["new_password"],
868
868
  rules: [{ required: true }, ({ getFieldValue }) => ({
869
- validator(_38, value) {
869
+ validator(_39, value) {
870
870
  if (!value || getFieldValue("new_password") === value) return Promise.resolve();
871
871
  return Promise.reject(new Error("Passwords do not match"));
872
872
  }
@@ -2606,19 +2606,19 @@ function Ut({
2606
2606
  const { defaultLayoutDeferred: Y, derivedPanelConstraints: Ee, layout: ce } = j.next;
2607
2607
  if (Y || Ee.length === 0)
2608
2608
  return;
2609
- const ut = R.panels.map(({ id: _38 }) => _38).join(",");
2610
- R.mutableState.layouts[ut] = ce, Ee.forEach((_38) => {
2611
- if (_38.collapsible) {
2609
+ const ut = R.panels.map(({ id: _39 }) => _39).join(",");
2610
+ R.mutableState.layouts[ut] = ce, Ee.forEach((_39) => {
2611
+ if (_39.collapsible) {
2612
2612
  const { layout: ge } = j.prev ?? {};
2613
2613
  if (ge) {
2614
2614
  const ft = I(
2615
- _38.collapsedSize,
2616
- ce[_38.panelId]
2615
+ _39.collapsedSize,
2616
+ ce[_39.panelId]
2617
2617
  ), dt = I(
2618
- _38.collapsedSize,
2619
- ge[_38.panelId]
2618
+ _39.collapsedSize,
2619
+ ge[_39.panelId]
2620
2620
  );
2621
- ft && !dt && (R.mutableState.expandedPanelSizes[_38.panelId] = ge[_38.panelId]);
2621
+ ft && !dt && (R.mutableState.expandedPanelSizes[_39.panelId] = ge[_39.panelId]);
2622
2622
  }
2623
2623
  }
2624
2624
  });
@@ -3997,7 +3997,7 @@ var parseInlineStyle = (styleText) => {
3997
3997
  return styleText.split(";").map((chunk) => chunk.trim()).filter(Boolean).reduce((acc, rule) => {
3998
3998
  const [rawKey, rawValue] = rule.split(":").map((part) => part.trim());
3999
3999
  if (!rawKey || !rawValue) return acc;
4000
- const camelKey = rawKey.replace(/-([a-z])/g, (_38, char) => char.toUpperCase());
4000
+ const camelKey = rawKey.replace(/-([a-z])/g, (_39, char) => char.toUpperCase());
4001
4001
  acc[camelKey] = rawValue;
4002
4002
  return acc;
4003
4003
  }, {});
@@ -6603,6 +6603,68 @@ var RelationsExplorer = ({ model, record, allModels, isActive = true }) => {
6603
6603
  ] })
6604
6604
  ] });
6605
6605
  };
6606
+
6607
+ // src/providers/constants.ts
6608
+ var API_URL3 = "/api";
6609
+
6610
+ // src/pages/dashboard/hooks/usePinRecord.ts
6611
+ function usePinRecord(resource, recordId) {
6612
+ const [pinned, setPinned] = React6.useState(null);
6613
+ const [loading, setLoading] = React6.useState(false);
6614
+ React6.useEffect(() => {
6615
+ if (!resource || recordId === void 0 || recordId === null || recordId === "") return;
6616
+ let cancelled = false;
6617
+ authenticatedFetch(
6618
+ `${API_URL3}/dashboard/pinned-records/check?resource=${encodeURIComponent(resource)}&record_id=${encodeURIComponent(String(recordId))}`
6619
+ ).then((r) => r.json()).then((d) => {
6620
+ if (!cancelled) setPinned(Boolean(d.pinned));
6621
+ }).catch(() => {
6622
+ if (!cancelled) setPinned(false);
6623
+ });
6624
+ return () => {
6625
+ cancelled = true;
6626
+ };
6627
+ }, [resource, recordId]);
6628
+ const pin = React6.useCallback(async () => {
6629
+ if (!resource || recordId === void 0) return;
6630
+ setLoading(true);
6631
+ try {
6632
+ await authenticatedFetch(`${API_URL3}/dashboard/pinned-records`, {
6633
+ method: "POST",
6634
+ headers: { "Content-Type": "application/json" },
6635
+ body: JSON.stringify({ resource, record_id: String(recordId) })
6636
+ });
6637
+ setPinned(true);
6638
+ } finally {
6639
+ setLoading(false);
6640
+ }
6641
+ }, [resource, recordId]);
6642
+ const unpin = React6.useCallback(async () => {
6643
+ if (!resource || recordId === void 0) return;
6644
+ setLoading(true);
6645
+ try {
6646
+ await authenticatedFetch(
6647
+ `${API_URL3}/dashboard/pinned-records/${encodeURIComponent(resource)}/${encodeURIComponent(String(recordId))}`,
6648
+ { method: "DELETE" }
6649
+ );
6650
+ setPinned(false);
6651
+ } finally {
6652
+ setLoading(false);
6653
+ }
6654
+ }, [resource, recordId]);
6655
+ const toggle = React6.useCallback(() => pinned ? unpin() : pin(), [pinned, pin, unpin]);
6656
+ return { pinned, loading, pin, unpin, toggle };
6657
+ }
6658
+ async function unpinRecords(resource, recordIds) {
6659
+ await Promise.all(
6660
+ recordIds.map(
6661
+ (id) => authenticatedFetch(
6662
+ `${API_URL3}/dashboard/pinned-records/${encodeURIComponent(resource)}/${encodeURIComponent(String(id))}`,
6663
+ { method: "DELETE" }
6664
+ )
6665
+ )
6666
+ );
6667
+ }
6606
6668
  var _15 = window._ || ((text) => text);
6607
6669
  var useShowActionsPreferences = (model, allModels, record, saveButtonProps) => {
6608
6670
  const apiUrl = core.useApiUrl();
@@ -6712,6 +6774,9 @@ var useShowActionsPreferences = (model, allModels, record, saveButtonProps) => {
6712
6774
  ] });
6713
6775
  const { id: urlId } = reactRouterDom.useParams();
6714
6776
  const effectiveRecord = record ?? (urlId ? { eid: Number(urlId) } : void 0);
6777
+ const recordId = effectiveRecord?.eid ?? effectiveRecord?.id ?? urlId;
6778
+ const resource = model.resource || model.name;
6779
+ const { pinned, loading: pinLoading, toggle: togglePin } = usePinRecord(resource, recordId);
6715
6780
  const { metadataButton, metadataModal } = useMetadataModal(model, allModels);
6716
6781
  const [exploreOpen, setExploreOpen] = React6.useState(false);
6717
6782
  const headerButtons = ({ defaultButtons }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -6719,6 +6784,15 @@ var useShowActionsPreferences = (model, allModels, record, saveButtonProps) => {
6719
6784
  metadataModal,
6720
6785
  /* @__PURE__ */ jsxRuntime.jsx(antd.Popover, { content: actionsSettingsContent, title: _15("Actions"), trigger: "hover", children: /* @__PURE__ */ jsxRuntime.jsx(antd.Button, { size: "small", icon: /* @__PURE__ */ jsxRuntime.jsx(icons.SettingOutlined, {}) }) }),
6721
6786
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginInlineStart: 10 } }),
6787
+ pinned !== null && /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: pinned ? _15("Unpin") : _15("Pin to dashboard"), children: /* @__PURE__ */ jsxRuntime.jsx(
6788
+ antd.Button,
6789
+ {
6790
+ size: "small",
6791
+ icon: pinned ? /* @__PURE__ */ jsxRuntime.jsx(icons.PushpinFilled, { style: { color: "#faad14" } }) : /* @__PURE__ */ jsxRuntime.jsx(icons.PushpinOutlined, {}),
6792
+ onClick: togglePin,
6793
+ loading: pinLoading
6794
+ }
6795
+ ) }),
6722
6796
  /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: _15("Explore"), children: /* @__PURE__ */ jsxRuntime.jsx(antd.Button, { size: "small", icon: /* @__PURE__ */ jsxRuntime.jsx(icons.ApartmentOutlined, {}), onClick: () => setExploreOpen(true) }) }),
6723
6797
  /* @__PURE__ */ jsxRuntime.jsx(
6724
6798
  antd.Modal,
@@ -7665,7 +7739,7 @@ var DynamicCreate = ({ model: modelProp, allModels, journeyCallbacks, injectedVa
7665
7739
  const prefix = useReadonly ? "pc" : "cr";
7666
7740
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: `1px solid ${token.colorBorder}`, borderRadius: 8, padding: "6px 6px", marginBottom: 6 }, children: [
7667
7741
  /* @__PURE__ */ jsxRuntime.jsx(Title2, { level: 5, style: { margin: 0, marginBottom: 6, color: "#1677ff" }, children: _23(section) }),
7668
- /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_39, colIdx) => {
7742
+ /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, rowIdx) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_40, colIdx) => {
7669
7743
  const cellItems = normalized.filter((r) => r.row === rowIdx + 1 && r.column === colIdx + 1);
7670
7744
  return /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "0 4px", verticalAlign: "top", width: `${100 / maxCol}%` }, children: cellItems.map(
7671
7745
  (item, idx) => useReadonly ? renderReadonlyCell(item, idx) : renderFormCell(item, idx)
@@ -8285,7 +8359,7 @@ var DynamicEdit = ({ model: modelProp, allModels, topContent, extraHeaderButtons
8285
8359
  },
8286
8360
  children: [
8287
8361
  /* @__PURE__ */ jsxRuntime.jsx(Title3, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _24(section) }),
8288
- /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_39, colIndex) => {
8362
+ /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_40, colIndex) => {
8289
8363
  const cellItems = normalized.filter(
8290
8364
  (item) => item.row === rowIndex + 1 && item.column === colIndex + 1
8291
8365
  );
@@ -8498,7 +8572,7 @@ var DynamicEdit = ({ model: modelProp, allModels, topContent, extraHeaderButtons
8498
8572
  const maxCol = Math.max(1, ...normalized.map((r) => r.column));
8499
8573
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0, border: `1px solid ${token.colorBorder}`, borderRadius: 8, padding: "2px 6px" }, children: [
8500
8574
  /* @__PURE__ */ jsxRuntime.jsx(Title3, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _24(section) }),
8501
- /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, ri) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_39, ci) => {
8575
+ /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, ri) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_40, ci) => {
8502
8576
  const cellItems = normalized.filter((item) => item.row === ri + 1 && item.column === ci + 1);
8503
8577
  return /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "0 4px", verticalAlign: "top", width: `${100 / maxCol}%` }, children: cellItems.map((item, idx) => {
8504
8578
  if (item.attribute_or_relation_type === "nlsentence") {
@@ -8819,7 +8893,7 @@ var useStandardShowTabs = (model, record, allModels, actionsState, editForm, ove
8819
8893
  },
8820
8894
  children: [
8821
8895
  /* @__PURE__ */ jsxRuntime.jsx(Title4, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _25(section) }),
8822
- /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_39, colIndex) => {
8896
+ /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_40, colIndex) => {
8823
8897
  const cellItems = normalized.filter(
8824
8898
  (item) => item.row === rowIndex + 1 && item.column === colIndex + 1
8825
8899
  );
@@ -9012,7 +9086,7 @@ var useStandardShowTabs = (model, record, allModels, actionsState, editForm, ove
9012
9086
  const maxCol = Math.max(1, ...normalized.map((r) => r.column));
9013
9087
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0, border: `1px solid ${token.colorBorder}`, borderRadius: 8, padding: "6px 6px" }, children: [
9014
9088
  /* @__PURE__ */ jsxRuntime.jsx(Title4, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _25(section) }),
9015
- /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, ri) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_39, ci) => {
9089
+ /* @__PURE__ */ jsxRuntime.jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, ri) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: Array.from({ length: maxCol }).map((_40, ci) => {
9016
9090
  const cellItems = normalized.filter((item) => item.row === ri + 1 && item.column === ci + 1);
9017
9091
  return /* @__PURE__ */ jsxRuntime.jsx("td", { style: { padding: "0 4px", verticalAlign: "top", width: `${100 / maxCol}%` }, children: cellItems.map((item) => {
9018
9092
  if (item.attribute_or_relation_type === "nlsentence") {
@@ -9917,7 +9991,7 @@ var RelatedObjectsEditableList = ({ rel, record, allModels }) => {
9917
9991
  setPage(p);
9918
9992
  }
9919
9993
  },
9920
- onShowSizeChange: (_38, newPageSize) => {
9994
+ onShowSizeChange: (_39, newPageSize) => {
9921
9995
  setPageSize(newPageSize);
9922
9996
  setPage(1);
9923
9997
  },
@@ -12377,7 +12451,7 @@ var RelatedObjectsTable = ({ rel, record, relatedModel, parentModel, showActions
12377
12451
  setCurrentPage(1);
12378
12452
  }
12379
12453
  },
12380
- onShowSizeChange: (_38, newPageSize) => {
12454
+ onShowSizeChange: (_39, newPageSize) => {
12381
12455
  if (newPageSize && newPageSize !== pageSize) {
12382
12456
  setPageSize(newPageSize);
12383
12457
  setCurrentPage(1);
@@ -12387,7 +12461,7 @@ var RelatedObjectsTable = ({ rel, record, relatedModel, parentModel, showActions
12387
12461
  size: "small",
12388
12462
  rowKey: (row) => row?.__relationKey || row?.eid || row?.id || JSON.stringify(row),
12389
12463
  locale: filteredRows.length === 0 ? { emptyText: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "inline-block", fontSize: 12, color: "#8c8c8c" }, children: _30("No related records") }) } : void 0,
12390
- onChange: (_38, filters, sorter, extra) => {
12464
+ onChange: (_39, filters, sorter, extra) => {
12391
12465
  const nextFilters = {};
12392
12466
  Object.entries(filters || {}).forEach(([key, values]) => {
12393
12467
  if (!values) return;
@@ -13748,7 +13822,7 @@ var renderRelationBlock = ({
13748
13822
  };
13749
13823
  var _32 = window._ || ((text) => text);
13750
13824
  var { Title: Title7 } = antd.Typography;
13751
- var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbedded = false, showActions = true, showCreate = true, layoutPreferenceType, listViewType, rowSelection, extraHeaderButtons, bulkActions }) => {
13825
+ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbedded = false, showActions = true, showCreate = true, layoutPreferenceType, listViewType, rowSelection, extraHeaderButtons, bulkActions, preferencesResourceOverride, defaultListVisible }) => {
13752
13826
  const model = useRoleFilteredModel(modelProp);
13753
13827
  applyI18nLabelsToModel(model);
13754
13828
  applyI18nLabelsToModels(allModels);
@@ -13759,6 +13833,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
13759
13833
  const invalidate = core.useInvalidate();
13760
13834
  const apiUrl = core.useApiUrl();
13761
13835
  const resourceIdentifier = resolveResourcePath(model.resource || model.name, allModels);
13836
+ const prefsKey = preferencesResourceOverride ?? resourceIdentifier;
13762
13837
  const { data: canDeleteData } = core.useCan({ resource: resourceIdentifier, action: "delete" });
13763
13838
  const { data: canEditData } = core.useCan({ resource: resourceIdentifier, action: "edit" });
13764
13839
  const canBulkDelete = canDeleteData?.can !== false;
@@ -13809,7 +13884,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
13809
13884
  const galleryImageHeight = viewSettings?.galleryImageHeight ?? 140;
13810
13885
  const calendarDateFieldOptions = React6.useMemo(() => getCalendarDateFieldOptions(model.fields), [model.fields]);
13811
13886
  const [localSearch, setLocalSearch] = React6.useState("");
13812
- const [listVisible, setListVisible] = React6.useState(true);
13887
+ const [listVisible, setListVisible] = React6.useState(defaultListVisible ?? true);
13813
13888
  const [isTdFlipped, setIsTdFlipped] = React6.useState(false);
13814
13889
  const [pageSize, setPageSize] = React6.useState(10);
13815
13890
  const [galleryPage, setGalleryPage] = React6.useState(1);
@@ -14418,7 +14493,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14418
14493
  }
14419
14494
  }, [numericFields, rankingFieldKey, rankingMode]);
14420
14495
  const resetLayoutDefaults = React6.useCallback(() => {
14421
- setListVisible(true);
14496
+ setListVisible(defaultListVisible ?? true);
14422
14497
  setAnalyzeOpen(false);
14423
14498
  setIsAnalyzeVertical(false);
14424
14499
  setIsAnalyzeFirst(false);
@@ -14427,7 +14502,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14427
14502
  setSelectedColumnKeys(null);
14428
14503
  setColumnOrder(null);
14429
14504
  setTotalsSummaryFunctions({});
14430
- }, [isEmbedded]);
14505
+ }, [isEmbedded, defaultListVisible]);
14431
14506
  const resetAnalyzeDefaults = React6.useCallback(() => {
14432
14507
  setCategoryField1(categoricalFields[0]?.key ?? null);
14433
14508
  setCategoryField2(categoricalFields.length > 1 ? categoricalFields[1].key : null);
@@ -14440,7 +14515,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14440
14515
  }, [categoricalFields, numericFields]);
14441
14516
  const persistCurrentViewNames = React6.useCallback(async (nextSelected, nextCurrent) => {
14442
14517
  try {
14443
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14518
+ const resourceKey = prefsKey;
14444
14519
  await authenticatedFetch(`${apiUrl}/views/preferences/view`, {
14445
14520
  method: "POST",
14446
14521
  headers: { "Content-Type": "application/json" },
@@ -14453,9 +14528,9 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14453
14528
  });
14454
14529
  } catch {
14455
14530
  }
14456
- }, [apiUrl, model.name, model.resource, allModels]);
14531
+ }, [apiUrl, model.name, model.resource, allModels, preferencesResourceOverride]);
14457
14532
  const loadViewNames = React6.useCallback(async () => {
14458
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14533
+ const resourceKey = prefsKey;
14459
14534
  setIsLoadingViewNames(true);
14460
14535
  try {
14461
14536
  const response = await authenticatedFetch(`${apiUrl}/views/preferences?resource=${encodeURIComponent(resourceKey)}&preference_type=__all__`);
@@ -14498,7 +14573,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14498
14573
  setViewNamesLoaded(true);
14499
14574
  setIsLoadingViewNames(false);
14500
14575
  }
14501
- }, [apiUrl, model.name, model.resource, allModels]);
14576
+ }, [apiUrl, model.name, model.resource, allModels, preferencesResourceOverride]);
14502
14577
  const openSaveViewModalFor = React6.useCallback((target) => {
14503
14578
  setSaveViewName(currentViewName || getDefaultViewName());
14504
14579
  setSaveViewAsNew(false);
@@ -14547,7 +14622,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14547
14622
  return;
14548
14623
  }
14549
14624
  try {
14550
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14625
+ const resourceKey = prefsKey;
14551
14626
  const response = await authenticatedFetch(`${apiUrl}/views/preferences/view`, {
14552
14627
  method: "POST",
14553
14628
  headers: { "Content-Type": "application/json" },
@@ -14571,7 +14646,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14571
14646
  okButtonProps: { danger: true },
14572
14647
  onOk: async () => {
14573
14648
  try {
14574
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14649
+ const resourceKey = prefsKey;
14575
14650
  const response = await authenticatedFetch(`${apiUrl}/views/preferences/view`, {
14576
14651
  method: "POST",
14577
14652
  headers: { "Content-Type": "application/json" },
@@ -14590,7 +14665,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14590
14665
  }, [apiUrl, currentViewName, model.name, model.resource, allModels, loadViewNames]);
14591
14666
  const persistLayoutPreferences = React6.useCallback(async (viewName) => {
14592
14667
  if (!resolvedLayoutPreferenceType) return;
14593
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14668
+ const resourceKey = prefsKey;
14594
14669
  const resolvedViewName = normalizeViewName(viewName);
14595
14670
  const preferences = {
14596
14671
  listVisible,
@@ -14631,9 +14706,9 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14631
14706
  } finally {
14632
14707
  setIsSavingLayoutPrefs(false);
14633
14708
  }
14634
- }, [apiUrl, analyzeOpen, columnFiltersSelected, columnOrder, columnSort, filtersCollapsed, filterRules, isAnalyzeFirst, isAnalyzeVertical, resolvedLayoutPreferenceType, listVisible, pageSize, selectedColumnKeys, totalsSummaryFunctions, model.name, model.resource, allModels]);
14709
+ }, [apiUrl, analyzeOpen, columnFiltersSelected, columnOrder, columnSort, filtersCollapsed, filterRules, isAnalyzeFirst, isAnalyzeVertical, resolvedLayoutPreferenceType, listVisible, pageSize, selectedColumnKeys, totalsSummaryFunctions, model.name, model.resource, allModels, preferencesResourceOverride]);
14635
14710
  const persistAnalyzePreferences = React6.useCallback(async (viewName) => {
14636
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14711
+ const resourceKey = prefsKey;
14637
14712
  const resolvedViewName = normalizeViewName(viewName);
14638
14713
  const preferences = {
14639
14714
  categoryField1,
@@ -14662,7 +14737,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14662
14737
  } finally {
14663
14738
  setIsSavingAnalyzePrefs(false);
14664
14739
  }
14665
- }, [apiUrl, categoryField1, categoryField2, chartType, selectedSeriesKeys, summaryFn, rankingMode, rankingFieldKey, rankingN, model.name, model.resource, allModels]);
14740
+ }, [apiUrl, categoryField1, categoryField2, chartType, selectedSeriesKeys, summaryFn, rankingMode, rankingFieldKey, rankingN, model.name, model.resource, allModels, preferencesResourceOverride]);
14666
14741
  const handleConfirmSaveView = React6.useCallback(async () => {
14667
14742
  if (!pendingSaveTarget) return;
14668
14743
  const viewName = normalizeViewName(saveViewName || currentViewName);
@@ -14706,7 +14781,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14706
14781
  resetAnalyzeDefaults();
14707
14782
  }, [currentViewName, resetAnalyzeDefaults, resetLayoutDefaults, viewNamesLoaded]);
14708
14783
  React6.useEffect(() => {
14709
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14784
+ const resourceKey = prefsKey;
14710
14785
  const viewKey = `${resourceKey}::${currentViewName}`;
14711
14786
  if (analyzePrefsResourceRef.current !== viewKey) {
14712
14787
  analyzePrefsLoadedRef.current = false;
@@ -14754,10 +14829,10 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14754
14829
  return () => {
14755
14830
  cancelled = true;
14756
14831
  };
14757
- }, [apiUrl, currentViewName, model.name, model.resource, allModels]);
14832
+ }, [apiUrl, currentViewName, model.name, model.resource, allModels, preferencesResourceOverride]);
14758
14833
  React6.useEffect(() => {
14759
14834
  if (!resolvedLayoutPreferenceType) return;
14760
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14835
+ const resourceKey = prefsKey;
14761
14836
  const viewKey = `${resourceKey}::${resolvedLayoutPreferenceType}::${currentViewName}`;
14762
14837
  if (layoutPrefsResourceRef.current !== viewKey) {
14763
14838
  layoutPrefsLoadedRef.current = false;
@@ -14771,7 +14846,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14771
14846
  let cancelled = false;
14772
14847
  const applyPrefs = (prefs) => {
14773
14848
  if (!prefs || typeof prefs !== "object") return false;
14774
- if ("listVisible" in prefs) setListVisible(Boolean(prefs.listVisible));
14849
+ if ("listVisible" in prefs && defaultListVisible !== false) setListVisible(Boolean(prefs.listVisible));
14775
14850
  if ("analyzeOpen" in prefs) setAnalyzeOpen(Boolean(prefs.analyzeOpen));
14776
14851
  if ("isAnalyzeVertical" in prefs) setIsAnalyzeVertical(Boolean(prefs.isAnalyzeVertical));
14777
14852
  if ("isAnalyzeFirst" in prefs) setIsAnalyzeFirst(Boolean(prefs.isAnalyzeFirst));
@@ -14839,7 +14914,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14839
14914
  return () => {
14840
14915
  cancelled = true;
14841
14916
  };
14842
- }, [apiUrl, currentViewName, resolvedLayoutPreferenceType, model.name, model.resource, allModels]);
14917
+ }, [apiUrl, currentViewName, resolvedLayoutPreferenceType, model.name, model.resource, allModels, preferencesResourceOverride]);
14843
14918
  const fetchAllRows = React6.useCallback(async () => {
14844
14919
  setIsAllRowsLoading(true);
14845
14920
  setAllRowsError(null);
@@ -15525,6 +15600,17 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
15525
15600
  body: JSON.stringify(clonePayload)
15526
15601
  });
15527
15602
  if (!resp.ok) throw new Error(`${_32("Clone failed for record")} ${id}`);
15603
+ } else if (actionKey === "__pin__") {
15604
+ await authenticatedFetch(`${apiUrl}/dashboard/pinned-records`, {
15605
+ method: "POST",
15606
+ headers: { "Content-Type": "application/json" },
15607
+ body: JSON.stringify({ resource, record_id: String(id) })
15608
+ });
15609
+ } else if (actionKey === "__unpin__") {
15610
+ await authenticatedFetch(
15611
+ `${apiUrl}/dashboard/pinned-records/${encodeURIComponent(resource)}/${encodeURIComponent(String(id))}`,
15612
+ { method: "DELETE" }
15613
+ );
15528
15614
  } else {
15529
15615
  const customAction = bulkActions?.find((a) => a.key === actionKey);
15530
15616
  if (customAction) await customAction.onExecuteOne(record);
@@ -15612,6 +15698,8 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
15612
15698
  if (bulkActions && bulkActions.length > 0) {
15613
15699
  bulkActions.forEach((a) => opts.push({ label: _32(a.label), value: a.key }));
15614
15700
  }
15701
+ opts.push({ label: _32("Pin selected"), value: "__pin__" });
15702
+ opts.push({ label: _32("Unpin selected"), value: "__unpin__" });
15615
15703
  if (canBulkDelete) {
15616
15704
  opts.push({ label: _32("Delete selected"), value: "__delete__" });
15617
15705
  }
@@ -17588,7 +17676,7 @@ var MultiPaneLayout = ({ children }) => {
17588
17676
  [openDetail]
17589
17677
  );
17590
17678
  const detailPaneContexts = React6.useMemo(
17591
- () => panes.map((_38, idx) => ({
17679
+ () => panes.map((_39, idx) => ({
17592
17680
  isInMultiPane: true,
17593
17681
  paneIndex: idx + 1,
17594
17682
  openDetail: (resource, id) => openDetail(idx + 1, resource, id)
@@ -17998,9 +18086,6 @@ httpClient.interceptors.request.use((config) => {
17998
18086
  }
17999
18087
  return config;
18000
18088
  });
18001
-
18002
- // src/providers/constants.ts
18003
- var API_URL3 = "/api";
18004
18089
  var API_BASE_URL = "/api";
18005
18090
  var ColorModeContextProvider = ({
18006
18091
  children
@@ -18170,6 +18255,773 @@ var LoginPage = ({ appTitle = "VeloIQ", logo }) => {
18170
18255
  }
18171
18256
  );
18172
18257
  };
18258
+ function useDashboardConfig() {
18259
+ const apiUrl = core.useApiUrl();
18260
+ const [config, setConfig] = React6.useState(null);
18261
+ const [enabled, setEnabled] = React6.useState(false);
18262
+ const [loading, setLoading] = React6.useState(true);
18263
+ const load = React6.useCallback(async () => {
18264
+ setLoading(true);
18265
+ try {
18266
+ const res = await authenticatedFetch(`${apiUrl}/dashboard/config`);
18267
+ if (!res.ok) {
18268
+ setLoading(false);
18269
+ return;
18270
+ }
18271
+ const data = await res.json();
18272
+ setEnabled(Boolean(data.enabled));
18273
+ if (data.enabled && data.dashboard) {
18274
+ setConfig(data.dashboard);
18275
+ }
18276
+ } catch {
18277
+ } finally {
18278
+ setLoading(false);
18279
+ }
18280
+ }, [apiUrl]);
18281
+ React6.useEffect(() => {
18282
+ load();
18283
+ }, [load]);
18284
+ const save = React6.useCallback(async (next) => {
18285
+ setConfig(next);
18286
+ try {
18287
+ await authenticatedFetch(`${apiUrl}/dashboard/config`, {
18288
+ method: "PUT",
18289
+ headers: { "Content-Type": "application/json" },
18290
+ body: JSON.stringify({ dashboard: next })
18291
+ });
18292
+ } catch {
18293
+ }
18294
+ }, [apiUrl]);
18295
+ return { config, enabled, loading, save, reload: load };
18296
+ }
18297
+ var { Text } = antd.Typography;
18298
+ var VIEW_TYPE_OPTIONS = [
18299
+ { label: "Default (from model schema)", value: "" },
18300
+ { label: "Table", value: "table" },
18301
+ { label: "Gallery", value: "gallery" },
18302
+ { label: "Calendar", value: "calendar" },
18303
+ { label: "Totals / Details", value: "totals-details" }
18304
+ ];
18305
+ var nextGridPosition = (cells) => {
18306
+ if (!cells.length) return { row: 0, col: 0 };
18307
+ const maxRow = Math.max(...cells.map((c) => c.row));
18308
+ const lastRowCells = cells.filter((c) => c.row === maxRow);
18309
+ if (lastRowCells.length < 2) return { row: maxRow, col: lastRowCells.length };
18310
+ return { row: maxRow + 1, col: 0 };
18311
+ };
18312
+ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
18313
+ const [form] = antd.Form.useForm();
18314
+ React6.useEffect(() => {
18315
+ if (!cell || !tabId) return;
18316
+ const tab = config.tabs.find((t) => t.id === tabId);
18317
+ form.setFieldsValue({
18318
+ tabName: tab?.name ?? "",
18319
+ row: cell.row + 1,
18320
+ col: cell.col + 1,
18321
+ view_type: cell.view_type ?? "",
18322
+ html_style: cell.html_style ?? "",
18323
+ min_width: cell.min_width ?? "",
18324
+ max_width: cell.max_width ?? "",
18325
+ min_height: cell.min_height ?? "",
18326
+ max_height: cell.max_height ?? ""
18327
+ });
18328
+ }, [cell, tabId, config, form]);
18329
+ const handleSave = () => {
18330
+ if (!cell || !tabId) return;
18331
+ const values = form.getFieldsValue();
18332
+ const newTabName = (values.tabName || "").trim() || config.tabs.find((t) => t.id === tabId)?.name || "";
18333
+ const updatedCell = {
18334
+ ...cell,
18335
+ row: Math.max(0, (values.row ?? 1) - 1),
18336
+ col: Math.max(0, (values.col ?? 1) - 1),
18337
+ view_type: values.view_type || null,
18338
+ html_style: values.html_style ?? "",
18339
+ min_width: values.min_width || null,
18340
+ max_width: values.max_width || null,
18341
+ min_height: values.min_height || null,
18342
+ max_height: values.max_height || null
18343
+ };
18344
+ const currentTab = config.tabs.find((t) => t.id === tabId);
18345
+ const nameUnchanged = currentTab?.name.trim().toLowerCase() === newTabName.toLowerCase();
18346
+ const targetTab = !nameUnchanged ? config.tabs.find((t) => t.id !== tabId && t.name.trim().toLowerCase() === newTabName.toLowerCase()) : void 0;
18347
+ let nextTabs;
18348
+ if (nameUnchanged) {
18349
+ nextTabs = config.tabs.map((tab) => {
18350
+ if (tab.id !== tabId) return tab;
18351
+ return { ...tab, cells: tab.cells.map((c) => c.id === cell.id ? updatedCell : c) };
18352
+ });
18353
+ } else if (targetTab) {
18354
+ const { row, col } = nextGridPosition(targetTab.cells);
18355
+ const repositionedCell = { ...updatedCell, row, col };
18356
+ nextTabs = config.tabs.map((tab) => {
18357
+ if (tab.id === tabId) {
18358
+ return { ...tab, cells: tab.cells.filter((c) => c.id !== cell.id) };
18359
+ }
18360
+ if (tab.id === targetTab.id) {
18361
+ return { ...tab, cells: [...tab.cells, repositionedCell] };
18362
+ }
18363
+ return tab;
18364
+ }).filter((tab) => tab.cells.length > 0);
18365
+ } else {
18366
+ const { row, col } = nextGridPosition([]);
18367
+ const repositionedCell = { ...updatedCell, row, col };
18368
+ const newTab = {
18369
+ id: crypto.randomUUID(),
18370
+ name: newTabName,
18371
+ module: currentTab?.module ?? "dashboard",
18372
+ cells: [repositionedCell]
18373
+ };
18374
+ nextTabs = [
18375
+ ...config.tabs.map((tab) => {
18376
+ if (tab.id !== tabId) return tab;
18377
+ return { ...tab, cells: tab.cells.filter((c) => c.id !== cell.id) };
18378
+ }).filter((tab) => tab.cells.length > 0),
18379
+ newTab
18380
+ ];
18381
+ }
18382
+ onSave({ ...config, tabs: nextTabs });
18383
+ onClose();
18384
+ };
18385
+ return /* @__PURE__ */ jsxRuntime.jsx(
18386
+ antd.Drawer,
18387
+ {
18388
+ title: `Configure cell: ${cell?.model ?? ""}`,
18389
+ placement: "right",
18390
+ width: 380,
18391
+ open,
18392
+ onClose,
18393
+ footer: /* @__PURE__ */ jsxRuntime.jsxs(antd.Space, { style: { justifyContent: "flex-end", width: "100%", display: "flex" }, children: [
18394
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Button, { onClick: onClose, children: "Cancel" }),
18395
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Button, { type: "primary", onClick: handleSave, children: "Save" })
18396
+ ] }),
18397
+ children: /* @__PURE__ */ jsxRuntime.jsxs(antd.Form, { form, layout: "vertical", size: "small", children: [
18398
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Divider, { orientation: "left", children: "Tab" }),
18399
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "tabName", label: "Tab name", children: /* @__PURE__ */ jsxRuntime.jsx(antd.Input, {}) }),
18400
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Divider, { orientation: "left", children: "Position" }),
18401
+ /* @__PURE__ */ jsxRuntime.jsxs(antd.Space, { children: [
18402
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "row", label: "Row", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.InputNumber, { min: 1, style: { width: 80 } }) }),
18403
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "col", label: "Column", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.InputNumber, { min: 1, style: { width: 80 } }) })
18404
+ ] }),
18405
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Divider, { orientation: "left", children: "View" }),
18406
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "view_type", label: "View type", children: /* @__PURE__ */ jsxRuntime.jsx(antd.Select, { options: VIEW_TYPE_OPTIONS }) }),
18407
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Divider, { orientation: "left", children: "Size" }),
18408
+ /* @__PURE__ */ jsxRuntime.jsxs(antd.Space, { wrap: true, children: [
18409
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "min_width", label: "Min width", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.Input, { placeholder: "e.g. 320px", style: { width: 130 } }) }),
18410
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "max_width", label: "Max width", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.Input, { placeholder: "e.g. 800px", style: { width: 130 } }) })
18411
+ ] }),
18412
+ /* @__PURE__ */ jsxRuntime.jsxs(antd.Space, { wrap: true, style: { marginTop: 8 }, children: [
18413
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "min_height", label: "Min height", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.Input, { placeholder: "e.g. 300px", style: { width: 130 } }) }),
18414
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Form.Item, { name: "max_height", label: "Max height", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.Input, { placeholder: "e.g. 600px", style: { width: 130 } }) })
18415
+ ] }),
18416
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Divider, { orientation: "left", children: "Style" }),
18417
+ /* @__PURE__ */ jsxRuntime.jsx(
18418
+ antd.Form.Item,
18419
+ {
18420
+ name: "html_style",
18421
+ label: /* @__PURE__ */ jsxRuntime.jsxs(Text, { children: [
18422
+ "HTML style ",
18423
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { type: "secondary", children: "(inline CSS)" })
18424
+ ] }),
18425
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18426
+ antd.Input.TextArea,
18427
+ {
18428
+ rows: 4,
18429
+ placeholder: "e.g. background-color: #f0f4ff; border-radius: 8px;",
18430
+ style: { fontFamily: "monospace", fontSize: 12 }
18431
+ }
18432
+ )
18433
+ }
18434
+ )
18435
+ ] })
18436
+ }
18437
+ );
18438
+ };
18439
+ var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, onConfigure, onMaximize, onMinimize }) => {
18440
+ const { token } = antd.theme.useToken();
18441
+ const model = findModelByName(allModels, cell.model);
18442
+ const cellStyle = {
18443
+ border: `1px solid ${token.colorBorderSecondary}`,
18444
+ borderRadius: token.borderRadiusLG,
18445
+ overflow: "hidden",
18446
+ display: "flex",
18447
+ flexDirection: "column",
18448
+ background: token.colorBgContainer,
18449
+ ...cell.min_width ? { minWidth: cell.min_width } : {},
18450
+ ...cell.max_width ? { maxWidth: cell.max_width } : {},
18451
+ ...cell.min_height ? { minHeight: cell.min_height } : {},
18452
+ ...cell.max_height ? { maxHeight: cell.max_height } : {},
18453
+ ...cell.html_style ? parseInlineStyle3(cell.html_style) : {},
18454
+ ...isMaximized ? { gridColumn: "1 / -1" } : {},
18455
+ ...isMinimized ? { minHeight: 0 } : {}
18456
+ };
18457
+ const toolbarStyle = {
18458
+ display: "flex",
18459
+ alignItems: "center",
18460
+ justifyContent: "space-between",
18461
+ padding: "2px 8px",
18462
+ gap: 2,
18463
+ borderBottom: `1px solid ${token.colorBorderSecondary}`,
18464
+ background: token.colorBgContainer,
18465
+ flexShrink: 0,
18466
+ minHeight: 32,
18467
+ position: "relative"
18468
+ };
18469
+ const resource = model?.resource || cell.model;
18470
+ const cellTitle = model?.label || cell.model;
18471
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cellStyle, className: "jm-dashboard-cell", children: [
18472
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
18473
+ .jm-dashboard-cell .jm-cell-actions { opacity: 0; transition: opacity 0.15s; }
18474
+ .jm-dashboard-cell:hover .jm-cell-actions { opacity: 1; }
18475
+ ` }),
18476
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: toolbarStyle, children: [
18477
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
18478
+ fontSize: token.fontSizeSM,
18479
+ fontWeight: token.fontWeightStrong,
18480
+ color: token.colorText,
18481
+ paddingLeft: 4,
18482
+ overflow: "hidden",
18483
+ textOverflow: "ellipsis",
18484
+ whiteSpace: "nowrap"
18485
+ }, children: cellTitle }),
18486
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "jm-cell-actions", style: { display: "flex", alignItems: "center", gap: 2 }, children: [
18487
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: "Configure cell", children: /* @__PURE__ */ jsxRuntime.jsx(
18488
+ antd.Button,
18489
+ {
18490
+ type: "text",
18491
+ size: "small",
18492
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.SettingOutlined, { style: { fontSize: 11 } }),
18493
+ onClick: onConfigure,
18494
+ style: { color: token.colorTextTertiary, padding: "0 4px", height: 22, minWidth: 22 }
18495
+ }
18496
+ ) }),
18497
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: "Open full page", children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/${resource}`, style: { color: token.colorTextTertiary, display: "flex", alignItems: "center", padding: "0 4px" }, children: /* @__PURE__ */ jsxRuntime.jsx(icons.LinkOutlined, { style: { fontSize: 11 } }) }) }),
18498
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: isMaximized ? "Restore" : "Maximize", children: /* @__PURE__ */ jsxRuntime.jsx(
18499
+ antd.Button,
18500
+ {
18501
+ type: "text",
18502
+ size: "small",
18503
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.FullscreenOutlined, { style: { fontSize: 11 } }),
18504
+ onClick: onMaximize,
18505
+ style: { color: token.colorTextTertiary, padding: "0 4px", height: 22, minWidth: 22 }
18506
+ }
18507
+ ) }),
18508
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: isMinimized ? "Restore" : "Minimize", children: /* @__PURE__ */ jsxRuntime.jsx(
18509
+ antd.Button,
18510
+ {
18511
+ type: "text",
18512
+ size: "small",
18513
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.MinusSquareOutlined, { style: { fontSize: 11 } }),
18514
+ onClick: onMinimize,
18515
+ style: { color: token.colorTextTertiary, padding: "0 4px", height: 22, minWidth: 22 }
18516
+ }
18517
+ ) })
18518
+ ] })
18519
+ ] }),
18520
+ !isMinimized && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, overflow: "auto", minHeight: 0 }, children: model ? /* @__PURE__ */ jsxRuntime.jsx(
18521
+ DynamicList,
18522
+ {
18523
+ model,
18524
+ allModels,
18525
+ isEmbedded: true,
18526
+ preferencesResourceOverride: `dashboard:${resource}`,
18527
+ defaultListVisible: false,
18528
+ listViewType: cell.view_type ? cell.view_type : model.listViewType
18529
+ }
18530
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
18531
+ antd.Empty,
18532
+ {
18533
+ description: `Model "${cell.model}" not found`,
18534
+ style: { padding: 24 },
18535
+ image: antd.Empty.PRESENTED_IMAGE_SIMPLE
18536
+ }
18537
+ ) })
18538
+ ] });
18539
+ };
18540
+ var DashboardTabContent = ({ tab, allModels, maximizedCellId, minimizedCellIds, onMaximize, onMinimize, onConfigure }) => {
18541
+ const cells = tab.cells;
18542
+ const numCols = React6.useMemo(() => {
18543
+ if (!cells.length) return 2;
18544
+ return Math.max(...cells.map((c) => c.col)) + 1;
18545
+ }, [cells]);
18546
+ const numRows = React6.useMemo(() => {
18547
+ if (!cells.length) return 1;
18548
+ return Math.max(...cells.map((c) => c.row)) + 1;
18549
+ }, [cells]);
18550
+ const visibleCells = maximizedCellId ? cells.filter((c) => c.id === maximizedCellId) : cells;
18551
+ const gridStyle = {
18552
+ display: "grid",
18553
+ gridTemplateColumns: maximizedCellId ? "1fr" : `repeat(${numCols}, 1fr)`,
18554
+ gridTemplateRows: maximizedCellId ? "1fr" : `repeat(${numRows}, minmax(320px, auto))`,
18555
+ gap: 12,
18556
+ padding: 12,
18557
+ height: "100%",
18558
+ boxSizing: "border-box"
18559
+ };
18560
+ if (!cells.length) {
18561
+ return /* @__PURE__ */ jsxRuntime.jsx(antd.Empty, { description: "No models in this tab", style: { padding: 48 } });
18562
+ }
18563
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: gridStyle, children: visibleCells.map((cell) => /* @__PURE__ */ jsxRuntime.jsx(
18564
+ "div",
18565
+ {
18566
+ style: {
18567
+ gridColumn: maximizedCellId ? "1 / -1" : `${cell.col + 1}`,
18568
+ gridRow: maximizedCellId ? "1 / -1" : `${cell.row + 1}`
18569
+ },
18570
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18571
+ DashboardGridCell,
18572
+ {
18573
+ cell,
18574
+ allModels,
18575
+ isMaximized: maximizedCellId === cell.id,
18576
+ isMinimized: minimizedCellIds.has(cell.id),
18577
+ onConfigure: () => onConfigure(cell),
18578
+ onMaximize: () => onMaximize(cell.id),
18579
+ onMinimize: () => onMinimize(cell.id)
18580
+ }
18581
+ )
18582
+ },
18583
+ cell.id
18584
+ )) });
18585
+ };
18586
+ var ViewsGrid = ({ config, allModels, onConfigChange }) => {
18587
+ const [maximizedCellId, setMaximizedCellId] = React6.useState(null);
18588
+ const [minimizedCellIds, setMinimizedCellIds] = React6.useState(/* @__PURE__ */ new Set());
18589
+ const [drawerSelection, setDrawerSelection] = React6.useState(null);
18590
+ const handleMaximize = React6.useCallback((cellId) => {
18591
+ setMaximizedCellId((prev) => prev === cellId ? null : cellId);
18592
+ }, []);
18593
+ const handleMinimize = React6.useCallback((cellId) => {
18594
+ setMinimizedCellIds((prev) => {
18595
+ const next = new Set(prev);
18596
+ if (next.has(cellId)) {
18597
+ next.delete(cellId);
18598
+ } else {
18599
+ next.add(cellId);
18600
+ }
18601
+ return next;
18602
+ });
18603
+ }, []);
18604
+ const handleOpenDrawer = React6.useCallback((tabId, cell) => {
18605
+ setDrawerSelection({ tabId, cell });
18606
+ }, []);
18607
+ const handleSaveConfig = React6.useCallback((nextConfig) => {
18608
+ onConfigChange(nextConfig);
18609
+ setDrawerSelection(null);
18610
+ }, [onConfigChange]);
18611
+ const tabItems = React6.useMemo(
18612
+ () => config.tabs.map((tab) => ({
18613
+ key: tab.id,
18614
+ label: tab.name,
18615
+ children: /* @__PURE__ */ jsxRuntime.jsx(
18616
+ DashboardTabContent,
18617
+ {
18618
+ tab,
18619
+ allModels,
18620
+ maximizedCellId,
18621
+ minimizedCellIds,
18622
+ onMaximize: handleMaximize,
18623
+ onMinimize: handleMinimize,
18624
+ onConfigure: (cell) => handleOpenDrawer(tab.id, cell)
18625
+ }
18626
+ )
18627
+ })),
18628
+ [config.tabs, allModels, maximizedCellId, minimizedCellIds, handleMaximize, handleMinimize, handleOpenDrawer]
18629
+ );
18630
+ if (!config.tabs.length) {
18631
+ return /* @__PURE__ */ jsxRuntime.jsx(antd.Empty, { description: "No tabs configured. Run veloiq add-dashboard to add models.", style: { padding: 48 } });
18632
+ }
18633
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
18634
+ /* @__PURE__ */ jsxRuntime.jsx(
18635
+ antd.Tabs,
18636
+ {
18637
+ items: tabItems,
18638
+ onChange: () => {
18639
+ setMaximizedCellId(null);
18640
+ setMinimizedCellIds(/* @__PURE__ */ new Set());
18641
+ },
18642
+ style: { height: "100%" },
18643
+ tabBarStyle: { paddingLeft: 12, marginBottom: 0 }
18644
+ }
18645
+ ),
18646
+ /* @__PURE__ */ jsxRuntime.jsx(
18647
+ CellConfigDrawer,
18648
+ {
18649
+ open: Boolean(drawerSelection),
18650
+ cell: drawerSelection?.cell ?? null,
18651
+ tabId: drawerSelection?.tabId ?? null,
18652
+ config,
18653
+ onClose: () => setDrawerSelection(null),
18654
+ onSave: handleSaveConfig
18655
+ }
18656
+ )
18657
+ ] });
18658
+ };
18659
+ function parseInlineStyle3(cssText) {
18660
+ const result = {};
18661
+ cssText.split(";").forEach((declaration) => {
18662
+ const idx = declaration.indexOf(":");
18663
+ if (idx < 0) return;
18664
+ const prop = declaration.slice(0, idx).trim();
18665
+ const value = declaration.slice(idx + 1).trim();
18666
+ if (!prop || !value) return;
18667
+ const camel = prop.replace(/-([a-z])/g, (_39, c) => c.toUpperCase());
18668
+ result[camel] = value;
18669
+ });
18670
+ return result;
18671
+ }
18672
+ function useRecentActivity(days) {
18673
+ const [data, setData] = React6.useState(null);
18674
+ const [loading, setLoading] = React6.useState(true);
18675
+ const load = React6.useCallback(async () => {
18676
+ setLoading(true);
18677
+ try {
18678
+ const params = days !== void 0 ? `?days=${days}` : "";
18679
+ const res = await authenticatedFetch(`${API_URL3}/dashboard/recent-activity${params}`);
18680
+ if (res.ok) setData(await res.json());
18681
+ } catch {
18682
+ } finally {
18683
+ setLoading(false);
18684
+ }
18685
+ }, [days]);
18686
+ React6.useEffect(() => {
18687
+ load();
18688
+ }, [load]);
18689
+ return { data, loading, reload: load };
18690
+ }
18691
+ var { Text: Text2, Title: Title9 } = antd.Typography;
18692
+ function relativeTime(iso) {
18693
+ if (!iso) return "";
18694
+ const diff = Date.now() - new Date(iso).getTime();
18695
+ const mins = Math.floor(diff / 6e4);
18696
+ if (mins < 1) return "just now";
18697
+ if (mins < 60) return `${mins}m ago`;
18698
+ const hrs = Math.floor(mins / 60);
18699
+ if (hrs < 24) return `${hrs}h ago`;
18700
+ const days = Math.floor(hrs / 24);
18701
+ if (days < 30) return `${days}d ago`;
18702
+ return new Date(iso).toLocaleDateString();
18703
+ }
18704
+ var RecentActivityPanel = () => {
18705
+ const { token } = antd.theme.useToken();
18706
+ const allModels = useAllModels();
18707
+ const [days, setDays] = React6.useState(30);
18708
+ const { data, loading, reload } = useRecentActivity(days);
18709
+ const groups = data?.groups ?? [];
18710
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "16px 0" }, children: [
18711
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, marginBottom: 20, paddingLeft: 4 }, children: [
18712
+ /* @__PURE__ */ jsxRuntime.jsx(Text2, { type: "secondary", children: "Show activity from the last" }),
18713
+ /* @__PURE__ */ jsxRuntime.jsx(
18714
+ antd.InputNumber,
18715
+ {
18716
+ min: 1,
18717
+ max: 365,
18718
+ value: days,
18719
+ onChange: (v) => v && setDays(v),
18720
+ style: { width: 72 },
18721
+ size: "small"
18722
+ }
18723
+ ),
18724
+ /* @__PURE__ */ jsxRuntime.jsx(Text2, { type: "secondary", children: "days" }),
18725
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: "Refresh", children: /* @__PURE__ */ jsxRuntime.jsx(
18726
+ icons.ReloadOutlined,
18727
+ {
18728
+ style: { color: token.colorTextTertiary, cursor: "pointer", fontSize: 13 },
18729
+ onClick: reload
18730
+ }
18731
+ ) }),
18732
+ data && /* @__PURE__ */ jsxRuntime.jsxs(Text2, { type: "secondary", style: { fontSize: 12 }, children: [
18733
+ groups.reduce((n, g) => n + g.records.length, 0),
18734
+ " records across ",
18735
+ groups.length,
18736
+ " models"
18737
+ ] })
18738
+ ] }),
18739
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", padding: 48 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.Spin, {}) }) : groups.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
18740
+ antd.Empty,
18741
+ {
18742
+ description: `No activity in the last ${days} days`,
18743
+ image: antd.Empty.PRESENTED_IMAGE_SIMPLE,
18744
+ style: { padding: 48 }
18745
+ }
18746
+ ) : /* @__PURE__ */ jsxRuntime.jsx(antd.Space, { direction: "vertical", size: 24, style: { width: "100%" }, children: groups.map((group) => {
18747
+ const model = findModelByName(allModels, group.resource);
18748
+ const tone = getModelTone(model?.name ?? group.resource);
18749
+ const label = model?.label ?? group.model_name;
18750
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
18751
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
18752
+ display: "flex",
18753
+ alignItems: "center",
18754
+ gap: 8,
18755
+ marginBottom: 6,
18756
+ paddingBottom: 6,
18757
+ borderBottom: `2px solid ${tone.solid}40`
18758
+ }, children: [
18759
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
18760
+ width: 10,
18761
+ height: 10,
18762
+ borderRadius: "50%",
18763
+ background: tone.solid,
18764
+ flexShrink: 0
18765
+ } }),
18766
+ /* @__PURE__ */ jsxRuntime.jsx(Title9, { level: 5, style: { margin: 0, color: tone.text }, children: label }),
18767
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tag, { color: tone.solid, style: { marginLeft: "auto", fontSize: 11 }, children: group.records.length })
18768
+ ] }),
18769
+ /* @__PURE__ */ jsxRuntime.jsx(
18770
+ antd.List,
18771
+ {
18772
+ size: "small",
18773
+ dataSource: group.records,
18774
+ renderItem: (rec) => {
18775
+ const timestamp = rec.updated_at || rec.created_at;
18776
+ const isNew = rec.created_at === rec.updated_at;
18777
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18778
+ antd.List.Item,
18779
+ {
18780
+ style: {
18781
+ padding: "4px 8px",
18782
+ borderRadius: token.borderRadius,
18783
+ transition: "background 0.15s"
18784
+ },
18785
+ className: "jm-activity-row",
18786
+ children: [
18787
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
18788
+ .jm-activity-row:hover { background: ${token.colorFillAlter}; }
18789
+ ` }),
18790
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, width: "100%" }, children: [
18791
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ClockCircleOutlined, { style: { color: token.colorTextTertiary, fontSize: 11, flexShrink: 0 } }),
18792
+ /* @__PURE__ */ jsxRuntime.jsx(
18793
+ reactRouterDom.Link,
18794
+ {
18795
+ to: `/${group.resource}/show/${rec.id}`,
18796
+ style: { flex: 1, color: token.colorText, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
18797
+ children: rec._label || `#${rec.id}`
18798
+ }
18799
+ ),
18800
+ isNew && /* @__PURE__ */ jsxRuntime.jsx(antd.Tag, { color: "green", style: { fontSize: 10, padding: "0 4px", lineHeight: "16px" }, children: "new" }),
18801
+ /* @__PURE__ */ jsxRuntime.jsx(Text2, { type: "secondary", style: { fontSize: 11, flexShrink: 0 }, children: relativeTime(timestamp) })
18802
+ ] })
18803
+ ]
18804
+ }
18805
+ );
18806
+ }
18807
+ }
18808
+ )
18809
+ ] }, group.resource);
18810
+ }) })
18811
+ ] });
18812
+ };
18813
+ var { Text: AntText, Title: AntTitle } = antd.Typography;
18814
+ function usePinnedRecords() {
18815
+ const [groups, setGroups] = React6.useState([]);
18816
+ const [loading, setLoading] = React6.useState(true);
18817
+ const load = React6.useCallback(async () => {
18818
+ setLoading(true);
18819
+ try {
18820
+ const res = await authenticatedFetch(`${API_URL3}/dashboard/pinned-records`);
18821
+ if (res.ok) {
18822
+ const data = await res.json();
18823
+ setGroups(data.groups ?? []);
18824
+ }
18825
+ } catch {
18826
+ } finally {
18827
+ setLoading(false);
18828
+ }
18829
+ }, []);
18830
+ React6__default.default.useEffect(() => {
18831
+ load();
18832
+ }, [load]);
18833
+ return { groups, loading, reload: load };
18834
+ }
18835
+ var PinnedRecordsPanel = () => {
18836
+ const { token } = antd.theme.useToken();
18837
+ const allModels = useAllModels();
18838
+ const { groups, loading, reload } = usePinnedRecords();
18839
+ const [unpinning, setUnpinning] = React6.useState(/* @__PURE__ */ new Set());
18840
+ const visibleGroups = groups.filter((g) => findModelByName(allModels, g.resource));
18841
+ const handleUnpin = React6.useCallback(async (resource, recordId) => {
18842
+ const key = `${resource}:${recordId}`;
18843
+ setUnpinning((prev) => new Set(prev).add(key));
18844
+ try {
18845
+ await unpinRecords(resource, [recordId]);
18846
+ await reload();
18847
+ } finally {
18848
+ setUnpinning((prev) => {
18849
+ const next = new Set(prev);
18850
+ next.delete(key);
18851
+ return next;
18852
+ });
18853
+ }
18854
+ }, [reload]);
18855
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "16px 0" }, children: [
18856
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, marginBottom: 20, paddingLeft: 4 }, children: [
18857
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PushpinFilled, { style: { color: "#faad14", fontSize: 14 } }),
18858
+ /* @__PURE__ */ jsxRuntime.jsx(AntText, { type: "secondary", children: "Records you've pinned across the app" }),
18859
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: "Refresh", children: /* @__PURE__ */ jsxRuntime.jsx(
18860
+ icons.ReloadOutlined,
18861
+ {
18862
+ style: { color: token.colorTextTertiary, cursor: "pointer", fontSize: 13 },
18863
+ onClick: reload
18864
+ }
18865
+ ) }),
18866
+ !loading && /* @__PURE__ */ jsxRuntime.jsxs(AntText, { type: "secondary", style: { fontSize: 12 }, children: [
18867
+ visibleGroups.reduce((n, g) => n + g.records.length, 0),
18868
+ " pins across ",
18869
+ visibleGroups.length,
18870
+ " models"
18871
+ ] })
18872
+ ] }),
18873
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", padding: 48 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.Spin, {}) }) : visibleGroups.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
18874
+ antd.Empty,
18875
+ {
18876
+ description: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
18877
+ "No pinned records yet.",
18878
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
18879
+ /* @__PURE__ */ jsxRuntime.jsxs(AntText, { type: "secondary", style: { fontSize: 12 }, children: [
18880
+ "Open any record and click the ",
18881
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PushpinOutlined, {}),
18882
+ " pin button to pin it here."
18883
+ ] })
18884
+ ] }),
18885
+ image: antd.Empty.PRESENTED_IMAGE_SIMPLE,
18886
+ style: { padding: 48 }
18887
+ }
18888
+ ) : /* @__PURE__ */ jsxRuntime.jsx(antd.Space, { direction: "vertical", size: 24, style: { width: "100%" }, children: visibleGroups.map((group) => {
18889
+ const model = findModelByName(allModels, group.resource);
18890
+ const tone = getModelTone(model?.name ?? group.resource);
18891
+ const label = model?.label ?? group.model_name;
18892
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
18893
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
18894
+ display: "flex",
18895
+ alignItems: "center",
18896
+ gap: 8,
18897
+ marginBottom: 6,
18898
+ paddingBottom: 6,
18899
+ borderBottom: `2px solid ${tone.solid}40`
18900
+ }, children: [
18901
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
18902
+ width: 10,
18903
+ height: 10,
18904
+ borderRadius: "50%",
18905
+ background: tone.solid,
18906
+ flexShrink: 0
18907
+ } }),
18908
+ /* @__PURE__ */ jsxRuntime.jsx(AntTitle, { level: 5, style: { margin: 0, color: tone.text }, children: label }),
18909
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tag, { color: tone.solid, style: { marginLeft: "auto", fontSize: 11 }, children: group.records.length })
18910
+ ] }),
18911
+ /* @__PURE__ */ jsxRuntime.jsx(
18912
+ antd.List,
18913
+ {
18914
+ size: "small",
18915
+ dataSource: group.records,
18916
+ renderItem: (rec) => {
18917
+ const key = `${group.resource}:${rec.id}`;
18918
+ return /* @__PURE__ */ jsxRuntime.jsxs(
18919
+ antd.List.Item,
18920
+ {
18921
+ style: {
18922
+ padding: "4px 8px",
18923
+ borderRadius: token.borderRadius,
18924
+ transition: "background 0.15s"
18925
+ },
18926
+ className: "jm-pin-row",
18927
+ children: [
18928
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `
18929
+ .jm-pin-row:hover { background: ${token.colorFillAlter}; }
18930
+ .jm-pin-row .jm-unpin-btn { opacity: 0; transition: opacity 0.15s; }
18931
+ .jm-pin-row:hover .jm-unpin-btn { opacity: 1; }
18932
+ ` }),
18933
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, width: "100%" }, children: [
18934
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PushpinFilled, { style: { color: "#faad14", fontSize: 11, flexShrink: 0 } }),
18935
+ /* @__PURE__ */ jsxRuntime.jsx(
18936
+ reactRouterDom.Link,
18937
+ {
18938
+ to: `/${group.resource}/show/${rec.id}`,
18939
+ style: { flex: 1, color: token.colorText, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
18940
+ children: rec._label || `#${rec.id}`
18941
+ }
18942
+ ),
18943
+ /* @__PURE__ */ jsxRuntime.jsx(antd.Tooltip, { title: "Unpin", children: /* @__PURE__ */ jsxRuntime.jsx(
18944
+ antd.Button,
18945
+ {
18946
+ className: "jm-unpin-btn",
18947
+ type: "text",
18948
+ size: "small",
18949
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.PushpinFilled, { style: { color: "#faad14" } }),
18950
+ loading: unpinning.has(key),
18951
+ onClick: () => handleUnpin(group.resource, rec.id),
18952
+ style: { color: token.colorTextTertiary, height: 20, minWidth: 20, padding: "0 4px" }
18953
+ }
18954
+ ) })
18955
+ ] })
18956
+ ]
18957
+ }
18958
+ );
18959
+ }
18960
+ }
18961
+ )
18962
+ ] }, group.resource);
18963
+ }) })
18964
+ ] });
18965
+ };
18966
+ var { Text: Text3 } = antd.Typography;
18967
+ var _38 = window._ || ((text) => text);
18968
+ var DashboardPage = () => {
18969
+ const { token } = antd.theme.useToken();
18970
+ const allModels = useAllModels();
18971
+ const { config, enabled, loading, save } = useDashboardConfig();
18972
+ if (loading) {
18973
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", padding: 64 }, children: /* @__PURE__ */ jsxRuntime.jsx(antd.Spin, {}) });
18974
+ }
18975
+ if (!enabled || !config) {
18976
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 48 }, children: /* @__PURE__ */ jsxRuntime.jsx(
18977
+ antd.Empty,
18978
+ {
18979
+ image: /* @__PURE__ */ jsxRuntime.jsx(icons.DashboardOutlined, { style: { fontSize: 48, color: token.colorTextTertiary } }),
18980
+ imageStyle: { height: 60 },
18981
+ description: /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
18982
+ "No dashboard configured.",
18983
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
18984
+ /* @__PURE__ */ jsxRuntime.jsxs(Text3, { type: "secondary", children: [
18985
+ "Run ",
18986
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "veloiq add-dashboard <model> \u2026" }),
18987
+ " to get started."
18988
+ ] })
18989
+ ] })
18990
+ }
18991
+ ) });
18992
+ }
18993
+ const tabs = [
18994
+ {
18995
+ key: "models_grid",
18996
+ label: _38("Models Grid"),
18997
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "calc(100vh - 140px)", overflow: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(
18998
+ ViewsGrid,
18999
+ {
19000
+ config,
19001
+ allModels,
19002
+ onConfigChange: save
19003
+ }
19004
+ ) })
19005
+ },
19006
+ {
19007
+ key: "recent_activity",
19008
+ label: _38("Recent Activity"),
19009
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "calc(100vh - 140px)", overflow: "auto", padding: "0 12px" }, children: /* @__PURE__ */ jsxRuntime.jsx(RecentActivityPanel, {}) })
19010
+ },
19011
+ {
19012
+ key: "pinned_records",
19013
+ label: _38("Pinned Records"),
19014
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: "calc(100vh - 140px)", overflow: "auto", padding: "0 12px" }, children: /* @__PURE__ */ jsxRuntime.jsx(PinnedRecordsPanel, {}) })
19015
+ }
19016
+ ];
19017
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "0 16px", height: "100%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
19018
+ antd.Tabs,
19019
+ {
19020
+ items: tabs,
19021
+ tabBarStyle: { marginBottom: 0 }
19022
+ }
19023
+ ) });
19024
+ };
18173
19025
 
18174
19026
  // src/utils/generateResources.ts
18175
19027
  function generateResources(models, moduleName, options = {}) {
@@ -18318,6 +19170,7 @@ exports.AllModelsProvider = AllModelsProvider;
18318
19170
  exports.ColorModeContext = ColorModeContext;
18319
19171
  exports.ColorModeContextProvider = ColorModeContextProvider;
18320
19172
  exports.CustomSider = CustomSider;
19173
+ exports.DashboardPage = DashboardPage;
18321
19174
  exports.DynamicCreate = DynamicCreate;
18322
19175
  exports.DynamicEdit = DynamicEdit;
18323
19176
  exports.DynamicList = DynamicList;
@@ -18332,12 +19185,15 @@ exports.LoginPage = LoginPage;
18332
19185
  exports.ModelHeading = ModelHeading;
18333
19186
  exports.MultiPaneLayout = MultiPaneLayout;
18334
19187
  exports.PaneNavigationContext = PaneNavigationContext;
19188
+ exports.PinnedRecordsPanel = PinnedRecordsPanel;
18335
19189
  exports.PrimaryShowContext = PrimaryShowContext;
19190
+ exports.RecentActivityPanel = RecentActivityPanel;
18336
19191
  exports.ReferenceField = ReferenceField;
18337
19192
  exports.ResourceContext = ResourceContext;
18338
19193
  exports.ShowFooterButtons = ShowFooterButtons;
18339
19194
  exports.StandardList = StandardList;
18340
19195
  exports.StandardShow = StandardShow;
19196
+ exports.ViewsGrid = ViewsGrid;
18341
19197
  exports.accessControlProvider = accessControlProvider;
18342
19198
  exports.authProvider = authProvider;
18343
19199
  exports.authSystemModels = authSystemModels;