@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.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import React6, { createContext, useContext, useMemo, useState, useRef, useEffect, useCallback, useLayoutEffect, useSyncExternalStore, useId, useImperativeHandle } from 'react';
2
2
  import { ThemedLayoutV2, Show, List, useForm, DeleteButton, useTable, RefineThemes, Breadcrumb as Breadcrumb$1, Create, useSelect, Edit, ListButton, EditButton, RefreshButton } from '@refinedev/antd';
3
3
  import { useMenu, useGo, useGetIdentity, useLogout, useOne, useApiUrl, useInvalidate, useCan, useCustom, useLogin, useWarnAboutChange } from '@refinedev/core';
4
- import { Typography, Menu, theme, Layout, Space, AutoComplete, Input, Spin, Grid, Form, Drawer, Modal, Button, Tooltip, Skeleton, message, Switch, Divider, Tabs, Alert, Card, Table, Select, DatePicker, InputNumber, Checkbox, Pagination, Collapse, Breadcrumb, Tree, ConfigProvider, Popover, Empty, Dropdown, Avatar, TimePicker, Tag, Upload } from 'antd';
5
- import { SearchOutlined, LockOutlined, LogoutOutlined, InfoCircleOutlined, SaveOutlined, UnorderedListOutlined, DownloadOutlined, SettingOutlined, PlusOutlined, LinkOutlined, ShareAltOutlined, BarChartOutlined, ColumnHeightOutlined, SwapOutlined, FilterOutlined, ArrowUpOutlined, ArrowDownOutlined, DeleteOutlined, ArrowLeftOutlined, ArrowRightOutlined, FileTextOutlined, BugOutlined, EyeOutlined, EditOutlined, FilePdfOutlined, CloseCircleOutlined, DownOutlined, UserOutlined, CheckCircleOutlined, CopyOutlined, ApartmentOutlined, SaveFilled, CalendarOutlined, MenuOutlined, MenuUnfoldOutlined, MenuFoldOutlined, LayoutOutlined, AppstoreOutlined, CommentOutlined, MinusSquareOutlined, FullscreenOutlined, CloseOutlined, DatabaseOutlined, ShopOutlined, BookOutlined, DashboardOutlined, UploadOutlined, FolderOutlined, FileOutlined, RightOutlined } from '@ant-design/icons';
4
+ import { Typography, Menu, theme, Layout, Space, AutoComplete, Input, Spin, Grid, Form, Drawer, Modal, Button, Tooltip, Skeleton, message, Switch, Divider, Tabs, Alert, Card, Table, Select, DatePicker, InputNumber, Checkbox, Pagination, Collapse, Breadcrumb, Tree, ConfigProvider, Empty, Tag, List as List$1, Popover, Dropdown, Avatar, TimePicker, Upload } from 'antd';
5
+ import { SearchOutlined, LockOutlined, LogoutOutlined, InfoCircleOutlined, SaveOutlined, UnorderedListOutlined, DownloadOutlined, SettingOutlined, PlusOutlined, LinkOutlined, ShareAltOutlined, BarChartOutlined, ColumnHeightOutlined, SwapOutlined, FilterOutlined, ArrowUpOutlined, ArrowDownOutlined, DeleteOutlined, ArrowLeftOutlined, ArrowRightOutlined, FileTextOutlined, BugOutlined, EyeOutlined, EditOutlined, FilePdfOutlined, CloseCircleOutlined, DownOutlined, UserOutlined, ReloadOutlined, ClockCircleOutlined, PushpinFilled, PushpinOutlined, DashboardOutlined, CheckCircleOutlined, CopyOutlined, ApartmentOutlined, SaveFilled, CalendarOutlined, MenuOutlined, MenuUnfoldOutlined, MenuFoldOutlined, LayoutOutlined, AppstoreOutlined, CommentOutlined, MinusSquareOutlined, FullscreenOutlined, CloseOutlined, DatabaseOutlined, ShopOutlined, BookOutlined, UploadOutlined, FolderOutlined, FileOutlined, RightOutlined } from '@ant-design/icons';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import { useNavigate, useParams, useSearchParams, useLocation, Link, UNSAFE_RouteContext } from 'react-router-dom';
8
8
  import { createPortal } from 'react-dom';
@@ -858,7 +858,7 @@ var LayoutWrapper = ({
858
858
  label: "Confirm Password",
859
859
  dependencies: ["new_password"],
860
860
  rules: [{ required: true }, ({ getFieldValue }) => ({
861
- validator(_38, value) {
861
+ validator(_39, value) {
862
862
  if (!value || getFieldValue("new_password") === value) return Promise.resolve();
863
863
  return Promise.reject(new Error("Passwords do not match"));
864
864
  }
@@ -2598,19 +2598,19 @@ function Ut({
2598
2598
  const { defaultLayoutDeferred: Y, derivedPanelConstraints: Ee, layout: ce } = j.next;
2599
2599
  if (Y || Ee.length === 0)
2600
2600
  return;
2601
- const ut = R.panels.map(({ id: _38 }) => _38).join(",");
2602
- R.mutableState.layouts[ut] = ce, Ee.forEach((_38) => {
2603
- if (_38.collapsible) {
2601
+ const ut = R.panels.map(({ id: _39 }) => _39).join(",");
2602
+ R.mutableState.layouts[ut] = ce, Ee.forEach((_39) => {
2603
+ if (_39.collapsible) {
2604
2604
  const { layout: ge } = j.prev ?? {};
2605
2605
  if (ge) {
2606
2606
  const ft = I(
2607
- _38.collapsedSize,
2608
- ce[_38.panelId]
2607
+ _39.collapsedSize,
2608
+ ce[_39.panelId]
2609
2609
  ), dt = I(
2610
- _38.collapsedSize,
2611
- ge[_38.panelId]
2610
+ _39.collapsedSize,
2611
+ ge[_39.panelId]
2612
2612
  );
2613
- ft && !dt && (R.mutableState.expandedPanelSizes[_38.panelId] = ge[_38.panelId]);
2613
+ ft && !dt && (R.mutableState.expandedPanelSizes[_39.panelId] = ge[_39.panelId]);
2614
2614
  }
2615
2615
  }
2616
2616
  });
@@ -3989,7 +3989,7 @@ var parseInlineStyle = (styleText) => {
3989
3989
  return styleText.split(";").map((chunk) => chunk.trim()).filter(Boolean).reduce((acc, rule) => {
3990
3990
  const [rawKey, rawValue] = rule.split(":").map((part) => part.trim());
3991
3991
  if (!rawKey || !rawValue) return acc;
3992
- const camelKey = rawKey.replace(/-([a-z])/g, (_38, char) => char.toUpperCase());
3992
+ const camelKey = rawKey.replace(/-([a-z])/g, (_39, char) => char.toUpperCase());
3993
3993
  acc[camelKey] = rawValue;
3994
3994
  return acc;
3995
3995
  }, {});
@@ -6595,6 +6595,68 @@ var RelationsExplorer = ({ model, record, allModels, isActive = true }) => {
6595
6595
  ] })
6596
6596
  ] });
6597
6597
  };
6598
+
6599
+ // src/providers/constants.ts
6600
+ var API_URL3 = "/api";
6601
+
6602
+ // src/pages/dashboard/hooks/usePinRecord.ts
6603
+ function usePinRecord(resource, recordId) {
6604
+ const [pinned, setPinned] = useState(null);
6605
+ const [loading, setLoading] = useState(false);
6606
+ useEffect(() => {
6607
+ if (!resource || recordId === void 0 || recordId === null || recordId === "") return;
6608
+ let cancelled = false;
6609
+ authenticatedFetch(
6610
+ `${API_URL3}/dashboard/pinned-records/check?resource=${encodeURIComponent(resource)}&record_id=${encodeURIComponent(String(recordId))}`
6611
+ ).then((r) => r.json()).then((d) => {
6612
+ if (!cancelled) setPinned(Boolean(d.pinned));
6613
+ }).catch(() => {
6614
+ if (!cancelled) setPinned(false);
6615
+ });
6616
+ return () => {
6617
+ cancelled = true;
6618
+ };
6619
+ }, [resource, recordId]);
6620
+ const pin = useCallback(async () => {
6621
+ if (!resource || recordId === void 0) return;
6622
+ setLoading(true);
6623
+ try {
6624
+ await authenticatedFetch(`${API_URL3}/dashboard/pinned-records`, {
6625
+ method: "POST",
6626
+ headers: { "Content-Type": "application/json" },
6627
+ body: JSON.stringify({ resource, record_id: String(recordId) })
6628
+ });
6629
+ setPinned(true);
6630
+ } finally {
6631
+ setLoading(false);
6632
+ }
6633
+ }, [resource, recordId]);
6634
+ const unpin = useCallback(async () => {
6635
+ if (!resource || recordId === void 0) return;
6636
+ setLoading(true);
6637
+ try {
6638
+ await authenticatedFetch(
6639
+ `${API_URL3}/dashboard/pinned-records/${encodeURIComponent(resource)}/${encodeURIComponent(String(recordId))}`,
6640
+ { method: "DELETE" }
6641
+ );
6642
+ setPinned(false);
6643
+ } finally {
6644
+ setLoading(false);
6645
+ }
6646
+ }, [resource, recordId]);
6647
+ const toggle = useCallback(() => pinned ? unpin() : pin(), [pinned, pin, unpin]);
6648
+ return { pinned, loading, pin, unpin, toggle };
6649
+ }
6650
+ async function unpinRecords(resource, recordIds) {
6651
+ await Promise.all(
6652
+ recordIds.map(
6653
+ (id) => authenticatedFetch(
6654
+ `${API_URL3}/dashboard/pinned-records/${encodeURIComponent(resource)}/${encodeURIComponent(String(id))}`,
6655
+ { method: "DELETE" }
6656
+ )
6657
+ )
6658
+ );
6659
+ }
6598
6660
  var _15 = window._ || ((text) => text);
6599
6661
  var useShowActionsPreferences = (model, allModels, record, saveButtonProps) => {
6600
6662
  const apiUrl = useApiUrl();
@@ -6704,6 +6766,9 @@ var useShowActionsPreferences = (model, allModels, record, saveButtonProps) => {
6704
6766
  ] });
6705
6767
  const { id: urlId } = useParams();
6706
6768
  const effectiveRecord = record ?? (urlId ? { eid: Number(urlId) } : void 0);
6769
+ const recordId = effectiveRecord?.eid ?? effectiveRecord?.id ?? urlId;
6770
+ const resource = model.resource || model.name;
6771
+ const { pinned, loading: pinLoading, toggle: togglePin } = usePinRecord(resource, recordId);
6707
6772
  const { metadataButton, metadataModal } = useMetadataModal(model, allModels);
6708
6773
  const [exploreOpen, setExploreOpen] = useState(false);
6709
6774
  const headerButtons = ({ defaultButtons }) => /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -6711,6 +6776,15 @@ var useShowActionsPreferences = (model, allModels, record, saveButtonProps) => {
6711
6776
  metadataModal,
6712
6777
  /* @__PURE__ */ jsx(Popover, { content: actionsSettingsContent, title: _15("Actions"), trigger: "hover", children: /* @__PURE__ */ jsx(Button, { size: "small", icon: /* @__PURE__ */ jsx(SettingOutlined, {}) }) }),
6713
6778
  /* @__PURE__ */ jsx("span", { style: { marginInlineStart: 10 } }),
6779
+ pinned !== null && /* @__PURE__ */ jsx(Tooltip, { title: pinned ? _15("Unpin") : _15("Pin to dashboard"), children: /* @__PURE__ */ jsx(
6780
+ Button,
6781
+ {
6782
+ size: "small",
6783
+ icon: pinned ? /* @__PURE__ */ jsx(PushpinFilled, { style: { color: "#faad14" } }) : /* @__PURE__ */ jsx(PushpinOutlined, {}),
6784
+ onClick: togglePin,
6785
+ loading: pinLoading
6786
+ }
6787
+ ) }),
6714
6788
  /* @__PURE__ */ jsx(Tooltip, { title: _15("Explore"), children: /* @__PURE__ */ jsx(Button, { size: "small", icon: /* @__PURE__ */ jsx(ApartmentOutlined, {}), onClick: () => setExploreOpen(true) }) }),
6715
6789
  /* @__PURE__ */ jsx(
6716
6790
  Modal,
@@ -7657,7 +7731,7 @@ var DynamicCreate = ({ model: modelProp, allModels, journeyCallbacks, injectedVa
7657
7731
  const prefix = useReadonly ? "pc" : "cr";
7658
7732
  return /* @__PURE__ */ jsxs("div", { style: { border: `1px solid ${token.colorBorder}`, borderRadius: 8, padding: "6px 6px", marginBottom: 6 }, children: [
7659
7733
  /* @__PURE__ */ jsx(Title2, { level: 5, style: { margin: 0, marginBottom: 6, color: "#1677ff" }, children: _23(section) }),
7660
- /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, rowIdx) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_39, colIdx) => {
7734
+ /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, rowIdx) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_40, colIdx) => {
7661
7735
  const cellItems = normalized.filter((r) => r.row === rowIdx + 1 && r.column === colIdx + 1);
7662
7736
  return /* @__PURE__ */ jsx("td", { style: { padding: "0 4px", verticalAlign: "top", width: `${100 / maxCol}%` }, children: cellItems.map(
7663
7737
  (item, idx) => useReadonly ? renderReadonlyCell(item, idx) : renderFormCell(item, idx)
@@ -8277,7 +8351,7 @@ var DynamicEdit = ({ model: modelProp, allModels, topContent, extraHeaderButtons
8277
8351
  },
8278
8352
  children: [
8279
8353
  /* @__PURE__ */ jsx(Title3, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _24(section) }),
8280
- /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, rowIndex) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_39, colIndex) => {
8354
+ /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, rowIndex) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_40, colIndex) => {
8281
8355
  const cellItems = normalized.filter(
8282
8356
  (item) => item.row === rowIndex + 1 && item.column === colIndex + 1
8283
8357
  );
@@ -8490,7 +8564,7 @@ var DynamicEdit = ({ model: modelProp, allModels, topContent, extraHeaderButtons
8490
8564
  const maxCol = Math.max(1, ...normalized.map((r) => r.column));
8491
8565
  return /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0, border: `1px solid ${token.colorBorder}`, borderRadius: 8, padding: "2px 6px" }, children: [
8492
8566
  /* @__PURE__ */ jsx(Title3, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _24(section) }),
8493
- /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, ri) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_39, ci) => {
8567
+ /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, ri) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_40, ci) => {
8494
8568
  const cellItems = normalized.filter((item) => item.row === ri + 1 && item.column === ci + 1);
8495
8569
  return /* @__PURE__ */ jsx("td", { style: { padding: "0 4px", verticalAlign: "top", width: `${100 / maxCol}%` }, children: cellItems.map((item, idx) => {
8496
8570
  if (item.attribute_or_relation_type === "nlsentence") {
@@ -8811,7 +8885,7 @@ var useStandardShowTabs = (model, record, allModels, actionsState, editForm, ove
8811
8885
  },
8812
8886
  children: [
8813
8887
  /* @__PURE__ */ jsx(Title4, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _25(section) }),
8814
- /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, rowIndex) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_39, colIndex) => {
8888
+ /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, rowIndex) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_40, colIndex) => {
8815
8889
  const cellItems = normalized.filter(
8816
8890
  (item) => item.row === rowIndex + 1 && item.column === colIndex + 1
8817
8891
  );
@@ -9004,7 +9078,7 @@ var useStandardShowTabs = (model, record, allModels, actionsState, editForm, ove
9004
9078
  const maxCol = Math.max(1, ...normalized.map((r) => r.column));
9005
9079
  return /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0, border: `1px solid ${token.colorBorder}`, borderRadius: 8, padding: "6px 6px" }, children: [
9006
9080
  /* @__PURE__ */ jsx(Title4, { level: 5, style: { margin: 0, color: "#1677ff" }, children: _25(section) }),
9007
- /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_38, ri) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_39, ci) => {
9081
+ /* @__PURE__ */ jsx("table", { style: { width: "100%", borderCollapse: "collapse", tableLayout: "fixed" }, children: /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: maxRow }).map((_39, ri) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: maxCol }).map((_40, ci) => {
9008
9082
  const cellItems = normalized.filter((item) => item.row === ri + 1 && item.column === ci + 1);
9009
9083
  return /* @__PURE__ */ jsx("td", { style: { padding: "0 4px", verticalAlign: "top", width: `${100 / maxCol}%` }, children: cellItems.map((item) => {
9010
9084
  if (item.attribute_or_relation_type === "nlsentence") {
@@ -9909,7 +9983,7 @@ var RelatedObjectsEditableList = ({ rel, record, allModels }) => {
9909
9983
  setPage(p);
9910
9984
  }
9911
9985
  },
9912
- onShowSizeChange: (_38, newPageSize) => {
9986
+ onShowSizeChange: (_39, newPageSize) => {
9913
9987
  setPageSize(newPageSize);
9914
9988
  setPage(1);
9915
9989
  },
@@ -12369,7 +12443,7 @@ var RelatedObjectsTable = ({ rel, record, relatedModel, parentModel, showActions
12369
12443
  setCurrentPage(1);
12370
12444
  }
12371
12445
  },
12372
- onShowSizeChange: (_38, newPageSize) => {
12446
+ onShowSizeChange: (_39, newPageSize) => {
12373
12447
  if (newPageSize && newPageSize !== pageSize) {
12374
12448
  setPageSize(newPageSize);
12375
12449
  setCurrentPage(1);
@@ -12379,7 +12453,7 @@ var RelatedObjectsTable = ({ rel, record, relatedModel, parentModel, showActions
12379
12453
  size: "small",
12380
12454
  rowKey: (row) => row?.__relationKey || row?.eid || row?.id || JSON.stringify(row),
12381
12455
  locale: filteredRows.length === 0 ? { emptyText: /* @__PURE__ */ jsx("span", { style: { display: "inline-block", fontSize: 12, color: "#8c8c8c" }, children: _30("No related records") }) } : void 0,
12382
- onChange: (_38, filters, sorter, extra) => {
12456
+ onChange: (_39, filters, sorter, extra) => {
12383
12457
  const nextFilters = {};
12384
12458
  Object.entries(filters || {}).forEach(([key, values]) => {
12385
12459
  if (!values) return;
@@ -13740,7 +13814,7 @@ var renderRelationBlock = ({
13740
13814
  };
13741
13815
  var _32 = window._ || ((text) => text);
13742
13816
  var { Title: Title7 } = Typography;
13743
- var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbedded = false, showActions = true, showCreate = true, layoutPreferenceType, listViewType, rowSelection, extraHeaderButtons, bulkActions }) => {
13817
+ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbedded = false, showActions = true, showCreate = true, layoutPreferenceType, listViewType, rowSelection, extraHeaderButtons, bulkActions, preferencesResourceOverride, defaultListVisible }) => {
13744
13818
  const model = useRoleFilteredModel(modelProp);
13745
13819
  applyI18nLabelsToModel(model);
13746
13820
  applyI18nLabelsToModels(allModels);
@@ -13751,6 +13825,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
13751
13825
  const invalidate = useInvalidate();
13752
13826
  const apiUrl = useApiUrl();
13753
13827
  const resourceIdentifier = resolveResourcePath(model.resource || model.name, allModels);
13828
+ const prefsKey = preferencesResourceOverride ?? resourceIdentifier;
13754
13829
  const { data: canDeleteData } = useCan({ resource: resourceIdentifier, action: "delete" });
13755
13830
  const { data: canEditData } = useCan({ resource: resourceIdentifier, action: "edit" });
13756
13831
  const canBulkDelete = canDeleteData?.can !== false;
@@ -13801,7 +13876,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
13801
13876
  const galleryImageHeight = viewSettings?.galleryImageHeight ?? 140;
13802
13877
  const calendarDateFieldOptions = useMemo(() => getCalendarDateFieldOptions(model.fields), [model.fields]);
13803
13878
  const [localSearch, setLocalSearch] = useState("");
13804
- const [listVisible, setListVisible] = useState(true);
13879
+ const [listVisible, setListVisible] = useState(defaultListVisible ?? true);
13805
13880
  const [isTdFlipped, setIsTdFlipped] = useState(false);
13806
13881
  const [pageSize, setPageSize] = useState(10);
13807
13882
  const [galleryPage, setGalleryPage] = useState(1);
@@ -14410,7 +14485,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14410
14485
  }
14411
14486
  }, [numericFields, rankingFieldKey, rankingMode]);
14412
14487
  const resetLayoutDefaults = useCallback(() => {
14413
- setListVisible(true);
14488
+ setListVisible(defaultListVisible ?? true);
14414
14489
  setAnalyzeOpen(false);
14415
14490
  setIsAnalyzeVertical(false);
14416
14491
  setIsAnalyzeFirst(false);
@@ -14419,7 +14494,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14419
14494
  setSelectedColumnKeys(null);
14420
14495
  setColumnOrder(null);
14421
14496
  setTotalsSummaryFunctions({});
14422
- }, [isEmbedded]);
14497
+ }, [isEmbedded, defaultListVisible]);
14423
14498
  const resetAnalyzeDefaults = useCallback(() => {
14424
14499
  setCategoryField1(categoricalFields[0]?.key ?? null);
14425
14500
  setCategoryField2(categoricalFields.length > 1 ? categoricalFields[1].key : null);
@@ -14432,7 +14507,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14432
14507
  }, [categoricalFields, numericFields]);
14433
14508
  const persistCurrentViewNames = useCallback(async (nextSelected, nextCurrent) => {
14434
14509
  try {
14435
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14510
+ const resourceKey = prefsKey;
14436
14511
  await authenticatedFetch(`${apiUrl}/views/preferences/view`, {
14437
14512
  method: "POST",
14438
14513
  headers: { "Content-Type": "application/json" },
@@ -14445,9 +14520,9 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14445
14520
  });
14446
14521
  } catch {
14447
14522
  }
14448
- }, [apiUrl, model.name, model.resource, allModels]);
14523
+ }, [apiUrl, model.name, model.resource, allModels, preferencesResourceOverride]);
14449
14524
  const loadViewNames = useCallback(async () => {
14450
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14525
+ const resourceKey = prefsKey;
14451
14526
  setIsLoadingViewNames(true);
14452
14527
  try {
14453
14528
  const response = await authenticatedFetch(`${apiUrl}/views/preferences?resource=${encodeURIComponent(resourceKey)}&preference_type=__all__`);
@@ -14490,7 +14565,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14490
14565
  setViewNamesLoaded(true);
14491
14566
  setIsLoadingViewNames(false);
14492
14567
  }
14493
- }, [apiUrl, model.name, model.resource, allModels]);
14568
+ }, [apiUrl, model.name, model.resource, allModels, preferencesResourceOverride]);
14494
14569
  const openSaveViewModalFor = useCallback((target) => {
14495
14570
  setSaveViewName(currentViewName || getDefaultViewName());
14496
14571
  setSaveViewAsNew(false);
@@ -14539,7 +14614,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14539
14614
  return;
14540
14615
  }
14541
14616
  try {
14542
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14617
+ const resourceKey = prefsKey;
14543
14618
  const response = await authenticatedFetch(`${apiUrl}/views/preferences/view`, {
14544
14619
  method: "POST",
14545
14620
  headers: { "Content-Type": "application/json" },
@@ -14563,7 +14638,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14563
14638
  okButtonProps: { danger: true },
14564
14639
  onOk: async () => {
14565
14640
  try {
14566
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14641
+ const resourceKey = prefsKey;
14567
14642
  const response = await authenticatedFetch(`${apiUrl}/views/preferences/view`, {
14568
14643
  method: "POST",
14569
14644
  headers: { "Content-Type": "application/json" },
@@ -14582,7 +14657,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14582
14657
  }, [apiUrl, currentViewName, model.name, model.resource, allModels, loadViewNames]);
14583
14658
  const persistLayoutPreferences = useCallback(async (viewName) => {
14584
14659
  if (!resolvedLayoutPreferenceType) return;
14585
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14660
+ const resourceKey = prefsKey;
14586
14661
  const resolvedViewName = normalizeViewName(viewName);
14587
14662
  const preferences = {
14588
14663
  listVisible,
@@ -14623,9 +14698,9 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14623
14698
  } finally {
14624
14699
  setIsSavingLayoutPrefs(false);
14625
14700
  }
14626
- }, [apiUrl, analyzeOpen, columnFiltersSelected, columnOrder, columnSort, filtersCollapsed, filterRules, isAnalyzeFirst, isAnalyzeVertical, resolvedLayoutPreferenceType, listVisible, pageSize, selectedColumnKeys, totalsSummaryFunctions, model.name, model.resource, allModels]);
14701
+ }, [apiUrl, analyzeOpen, columnFiltersSelected, columnOrder, columnSort, filtersCollapsed, filterRules, isAnalyzeFirst, isAnalyzeVertical, resolvedLayoutPreferenceType, listVisible, pageSize, selectedColumnKeys, totalsSummaryFunctions, model.name, model.resource, allModels, preferencesResourceOverride]);
14627
14702
  const persistAnalyzePreferences = useCallback(async (viewName) => {
14628
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14703
+ const resourceKey = prefsKey;
14629
14704
  const resolvedViewName = normalizeViewName(viewName);
14630
14705
  const preferences = {
14631
14706
  categoryField1,
@@ -14654,7 +14729,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14654
14729
  } finally {
14655
14730
  setIsSavingAnalyzePrefs(false);
14656
14731
  }
14657
- }, [apiUrl, categoryField1, categoryField2, chartType, selectedSeriesKeys, summaryFn, rankingMode, rankingFieldKey, rankingN, model.name, model.resource, allModels]);
14732
+ }, [apiUrl, categoryField1, categoryField2, chartType, selectedSeriesKeys, summaryFn, rankingMode, rankingFieldKey, rankingN, model.name, model.resource, allModels, preferencesResourceOverride]);
14658
14733
  const handleConfirmSaveView = useCallback(async () => {
14659
14734
  if (!pendingSaveTarget) return;
14660
14735
  const viewName = normalizeViewName(saveViewName || currentViewName);
@@ -14698,7 +14773,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14698
14773
  resetAnalyzeDefaults();
14699
14774
  }, [currentViewName, resetAnalyzeDefaults, resetLayoutDefaults, viewNamesLoaded]);
14700
14775
  useEffect(() => {
14701
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14776
+ const resourceKey = prefsKey;
14702
14777
  const viewKey = `${resourceKey}::${currentViewName}`;
14703
14778
  if (analyzePrefsResourceRef.current !== viewKey) {
14704
14779
  analyzePrefsLoadedRef.current = false;
@@ -14746,10 +14821,10 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14746
14821
  return () => {
14747
14822
  cancelled = true;
14748
14823
  };
14749
- }, [apiUrl, currentViewName, model.name, model.resource, allModels]);
14824
+ }, [apiUrl, currentViewName, model.name, model.resource, allModels, preferencesResourceOverride]);
14750
14825
  useEffect(() => {
14751
14826
  if (!resolvedLayoutPreferenceType) return;
14752
- const resourceKey = resolveResourcePath(model.resource || model.name, allModels);
14827
+ const resourceKey = prefsKey;
14753
14828
  const viewKey = `${resourceKey}::${resolvedLayoutPreferenceType}::${currentViewName}`;
14754
14829
  if (layoutPrefsResourceRef.current !== viewKey) {
14755
14830
  layoutPrefsLoadedRef.current = false;
@@ -14763,7 +14838,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14763
14838
  let cancelled = false;
14764
14839
  const applyPrefs = (prefs) => {
14765
14840
  if (!prefs || typeof prefs !== "object") return false;
14766
- if ("listVisible" in prefs) setListVisible(Boolean(prefs.listVisible));
14841
+ if ("listVisible" in prefs && defaultListVisible !== false) setListVisible(Boolean(prefs.listVisible));
14767
14842
  if ("analyzeOpen" in prefs) setAnalyzeOpen(Boolean(prefs.analyzeOpen));
14768
14843
  if ("isAnalyzeVertical" in prefs) setIsAnalyzeVertical(Boolean(prefs.isAnalyzeVertical));
14769
14844
  if ("isAnalyzeFirst" in prefs) setIsAnalyzeFirst(Boolean(prefs.isAnalyzeFirst));
@@ -14831,7 +14906,7 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
14831
14906
  return () => {
14832
14907
  cancelled = true;
14833
14908
  };
14834
- }, [apiUrl, currentViewName, resolvedLayoutPreferenceType, model.name, model.resource, allModels]);
14909
+ }, [apiUrl, currentViewName, resolvedLayoutPreferenceType, model.name, model.resource, allModels, preferencesResourceOverride]);
14835
14910
  const fetchAllRows = useCallback(async () => {
14836
14911
  setIsAllRowsLoading(true);
14837
14912
  setAllRowsError(null);
@@ -15517,6 +15592,17 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
15517
15592
  body: JSON.stringify(clonePayload)
15518
15593
  });
15519
15594
  if (!resp.ok) throw new Error(`${_32("Clone failed for record")} ${id}`);
15595
+ } else if (actionKey === "__pin__") {
15596
+ await authenticatedFetch(`${apiUrl}/dashboard/pinned-records`, {
15597
+ method: "POST",
15598
+ headers: { "Content-Type": "application/json" },
15599
+ body: JSON.stringify({ resource, record_id: String(id) })
15600
+ });
15601
+ } else if (actionKey === "__unpin__") {
15602
+ await authenticatedFetch(
15603
+ `${apiUrl}/dashboard/pinned-records/${encodeURIComponent(resource)}/${encodeURIComponent(String(id))}`,
15604
+ { method: "DELETE" }
15605
+ );
15520
15606
  } else {
15521
15607
  const customAction = bulkActions?.find((a) => a.key === actionKey);
15522
15608
  if (customAction) await customAction.onExecuteOne(record);
@@ -15604,6 +15690,8 @@ var DynamicList = ({ model: modelProp, allModels, filter, relationConfig, isEmbe
15604
15690
  if (bulkActions && bulkActions.length > 0) {
15605
15691
  bulkActions.forEach((a) => opts.push({ label: _32(a.label), value: a.key }));
15606
15692
  }
15693
+ opts.push({ label: _32("Pin selected"), value: "__pin__" });
15694
+ opts.push({ label: _32("Unpin selected"), value: "__unpin__" });
15607
15695
  if (canBulkDelete) {
15608
15696
  opts.push({ label: _32("Delete selected"), value: "__delete__" });
15609
15697
  }
@@ -17580,7 +17668,7 @@ var MultiPaneLayout = ({ children }) => {
17580
17668
  [openDetail]
17581
17669
  );
17582
17670
  const detailPaneContexts = useMemo(
17583
- () => panes.map((_38, idx) => ({
17671
+ () => panes.map((_39, idx) => ({
17584
17672
  isInMultiPane: true,
17585
17673
  paneIndex: idx + 1,
17586
17674
  openDetail: (resource, id) => openDetail(idx + 1, resource, id)
@@ -17990,9 +18078,6 @@ httpClient.interceptors.request.use((config) => {
17990
18078
  }
17991
18079
  return config;
17992
18080
  });
17993
-
17994
- // src/providers/constants.ts
17995
- var API_URL3 = "/api";
17996
18081
  var API_BASE_URL = "/api";
17997
18082
  var ColorModeContextProvider = ({
17998
18083
  children
@@ -18162,6 +18247,773 @@ var LoginPage = ({ appTitle = "VeloIQ", logo }) => {
18162
18247
  }
18163
18248
  );
18164
18249
  };
18250
+ function useDashboardConfig() {
18251
+ const apiUrl = useApiUrl();
18252
+ const [config, setConfig] = useState(null);
18253
+ const [enabled, setEnabled] = useState(false);
18254
+ const [loading, setLoading] = useState(true);
18255
+ const load = useCallback(async () => {
18256
+ setLoading(true);
18257
+ try {
18258
+ const res = await authenticatedFetch(`${apiUrl}/dashboard/config`);
18259
+ if (!res.ok) {
18260
+ setLoading(false);
18261
+ return;
18262
+ }
18263
+ const data = await res.json();
18264
+ setEnabled(Boolean(data.enabled));
18265
+ if (data.enabled && data.dashboard) {
18266
+ setConfig(data.dashboard);
18267
+ }
18268
+ } catch {
18269
+ } finally {
18270
+ setLoading(false);
18271
+ }
18272
+ }, [apiUrl]);
18273
+ useEffect(() => {
18274
+ load();
18275
+ }, [load]);
18276
+ const save = useCallback(async (next) => {
18277
+ setConfig(next);
18278
+ try {
18279
+ await authenticatedFetch(`${apiUrl}/dashboard/config`, {
18280
+ method: "PUT",
18281
+ headers: { "Content-Type": "application/json" },
18282
+ body: JSON.stringify({ dashboard: next })
18283
+ });
18284
+ } catch {
18285
+ }
18286
+ }, [apiUrl]);
18287
+ return { config, enabled, loading, save, reload: load };
18288
+ }
18289
+ var { Text } = Typography;
18290
+ var VIEW_TYPE_OPTIONS = [
18291
+ { label: "Default (from model schema)", value: "" },
18292
+ { label: "Table", value: "table" },
18293
+ { label: "Gallery", value: "gallery" },
18294
+ { label: "Calendar", value: "calendar" },
18295
+ { label: "Totals / Details", value: "totals-details" }
18296
+ ];
18297
+ var nextGridPosition = (cells) => {
18298
+ if (!cells.length) return { row: 0, col: 0 };
18299
+ const maxRow = Math.max(...cells.map((c) => c.row));
18300
+ const lastRowCells = cells.filter((c) => c.row === maxRow);
18301
+ if (lastRowCells.length < 2) return { row: maxRow, col: lastRowCells.length };
18302
+ return { row: maxRow + 1, col: 0 };
18303
+ };
18304
+ var CellConfigDrawer = ({ open, cell, tabId, config, onClose, onSave }) => {
18305
+ const [form] = Form.useForm();
18306
+ useEffect(() => {
18307
+ if (!cell || !tabId) return;
18308
+ const tab = config.tabs.find((t) => t.id === tabId);
18309
+ form.setFieldsValue({
18310
+ tabName: tab?.name ?? "",
18311
+ row: cell.row + 1,
18312
+ col: cell.col + 1,
18313
+ view_type: cell.view_type ?? "",
18314
+ html_style: cell.html_style ?? "",
18315
+ min_width: cell.min_width ?? "",
18316
+ max_width: cell.max_width ?? "",
18317
+ min_height: cell.min_height ?? "",
18318
+ max_height: cell.max_height ?? ""
18319
+ });
18320
+ }, [cell, tabId, config, form]);
18321
+ const handleSave = () => {
18322
+ if (!cell || !tabId) return;
18323
+ const values = form.getFieldsValue();
18324
+ const newTabName = (values.tabName || "").trim() || config.tabs.find((t) => t.id === tabId)?.name || "";
18325
+ const updatedCell = {
18326
+ ...cell,
18327
+ row: Math.max(0, (values.row ?? 1) - 1),
18328
+ col: Math.max(0, (values.col ?? 1) - 1),
18329
+ view_type: values.view_type || null,
18330
+ html_style: values.html_style ?? "",
18331
+ min_width: values.min_width || null,
18332
+ max_width: values.max_width || null,
18333
+ min_height: values.min_height || null,
18334
+ max_height: values.max_height || null
18335
+ };
18336
+ const currentTab = config.tabs.find((t) => t.id === tabId);
18337
+ const nameUnchanged = currentTab?.name.trim().toLowerCase() === newTabName.toLowerCase();
18338
+ const targetTab = !nameUnchanged ? config.tabs.find((t) => t.id !== tabId && t.name.trim().toLowerCase() === newTabName.toLowerCase()) : void 0;
18339
+ let nextTabs;
18340
+ if (nameUnchanged) {
18341
+ nextTabs = config.tabs.map((tab) => {
18342
+ if (tab.id !== tabId) return tab;
18343
+ return { ...tab, cells: tab.cells.map((c) => c.id === cell.id ? updatedCell : c) };
18344
+ });
18345
+ } else if (targetTab) {
18346
+ const { row, col } = nextGridPosition(targetTab.cells);
18347
+ const repositionedCell = { ...updatedCell, row, col };
18348
+ nextTabs = config.tabs.map((tab) => {
18349
+ if (tab.id === tabId) {
18350
+ return { ...tab, cells: tab.cells.filter((c) => c.id !== cell.id) };
18351
+ }
18352
+ if (tab.id === targetTab.id) {
18353
+ return { ...tab, cells: [...tab.cells, repositionedCell] };
18354
+ }
18355
+ return tab;
18356
+ }).filter((tab) => tab.cells.length > 0);
18357
+ } else {
18358
+ const { row, col } = nextGridPosition([]);
18359
+ const repositionedCell = { ...updatedCell, row, col };
18360
+ const newTab = {
18361
+ id: crypto.randomUUID(),
18362
+ name: newTabName,
18363
+ module: currentTab?.module ?? "dashboard",
18364
+ cells: [repositionedCell]
18365
+ };
18366
+ nextTabs = [
18367
+ ...config.tabs.map((tab) => {
18368
+ if (tab.id !== tabId) return tab;
18369
+ return { ...tab, cells: tab.cells.filter((c) => c.id !== cell.id) };
18370
+ }).filter((tab) => tab.cells.length > 0),
18371
+ newTab
18372
+ ];
18373
+ }
18374
+ onSave({ ...config, tabs: nextTabs });
18375
+ onClose();
18376
+ };
18377
+ return /* @__PURE__ */ jsx(
18378
+ Drawer,
18379
+ {
18380
+ title: `Configure cell: ${cell?.model ?? ""}`,
18381
+ placement: "right",
18382
+ width: 380,
18383
+ open,
18384
+ onClose,
18385
+ footer: /* @__PURE__ */ jsxs(Space, { style: { justifyContent: "flex-end", width: "100%", display: "flex" }, children: [
18386
+ /* @__PURE__ */ jsx(Button, { onClick: onClose, children: "Cancel" }),
18387
+ /* @__PURE__ */ jsx(Button, { type: "primary", onClick: handleSave, children: "Save" })
18388
+ ] }),
18389
+ children: /* @__PURE__ */ jsxs(Form, { form, layout: "vertical", size: "small", children: [
18390
+ /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Tab" }),
18391
+ /* @__PURE__ */ jsx(Form.Item, { name: "tabName", label: "Tab name", children: /* @__PURE__ */ jsx(Input, {}) }),
18392
+ /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Position" }),
18393
+ /* @__PURE__ */ jsxs(Space, { children: [
18394
+ /* @__PURE__ */ jsx(Form.Item, { name: "row", label: "Row", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsx(InputNumber, { min: 1, style: { width: 80 } }) }),
18395
+ /* @__PURE__ */ jsx(Form.Item, { name: "col", label: "Column", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsx(InputNumber, { min: 1, style: { width: 80 } }) })
18396
+ ] }),
18397
+ /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "View" }),
18398
+ /* @__PURE__ */ jsx(Form.Item, { name: "view_type", label: "View type", children: /* @__PURE__ */ jsx(Select, { options: VIEW_TYPE_OPTIONS }) }),
18399
+ /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Size" }),
18400
+ /* @__PURE__ */ jsxs(Space, { wrap: true, children: [
18401
+ /* @__PURE__ */ jsx(Form.Item, { name: "min_width", label: "Min width", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsx(Input, { placeholder: "e.g. 320px", style: { width: 130 } }) }),
18402
+ /* @__PURE__ */ jsx(Form.Item, { name: "max_width", label: "Max width", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsx(Input, { placeholder: "e.g. 800px", style: { width: 130 } }) })
18403
+ ] }),
18404
+ /* @__PURE__ */ jsxs(Space, { wrap: true, style: { marginTop: 8 }, children: [
18405
+ /* @__PURE__ */ jsx(Form.Item, { name: "min_height", label: "Min height", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsx(Input, { placeholder: "e.g. 300px", style: { width: 130 } }) }),
18406
+ /* @__PURE__ */ jsx(Form.Item, { name: "max_height", label: "Max height", style: { marginBottom: 0 }, children: /* @__PURE__ */ jsx(Input, { placeholder: "e.g. 600px", style: { width: 130 } }) })
18407
+ ] }),
18408
+ /* @__PURE__ */ jsx(Divider, { orientation: "left", children: "Style" }),
18409
+ /* @__PURE__ */ jsx(
18410
+ Form.Item,
18411
+ {
18412
+ name: "html_style",
18413
+ label: /* @__PURE__ */ jsxs(Text, { children: [
18414
+ "HTML style ",
18415
+ /* @__PURE__ */ jsx(Text, { type: "secondary", children: "(inline CSS)" })
18416
+ ] }),
18417
+ children: /* @__PURE__ */ jsx(
18418
+ Input.TextArea,
18419
+ {
18420
+ rows: 4,
18421
+ placeholder: "e.g. background-color: #f0f4ff; border-radius: 8px;",
18422
+ style: { fontFamily: "monospace", fontSize: 12 }
18423
+ }
18424
+ )
18425
+ }
18426
+ )
18427
+ ] })
18428
+ }
18429
+ );
18430
+ };
18431
+ var DashboardGridCell = ({ cell, allModels, isMaximized, isMinimized, onConfigure, onMaximize, onMinimize }) => {
18432
+ const { token } = theme.useToken();
18433
+ const model = findModelByName(allModels, cell.model);
18434
+ const cellStyle = {
18435
+ border: `1px solid ${token.colorBorderSecondary}`,
18436
+ borderRadius: token.borderRadiusLG,
18437
+ overflow: "hidden",
18438
+ display: "flex",
18439
+ flexDirection: "column",
18440
+ background: token.colorBgContainer,
18441
+ ...cell.min_width ? { minWidth: cell.min_width } : {},
18442
+ ...cell.max_width ? { maxWidth: cell.max_width } : {},
18443
+ ...cell.min_height ? { minHeight: cell.min_height } : {},
18444
+ ...cell.max_height ? { maxHeight: cell.max_height } : {},
18445
+ ...cell.html_style ? parseInlineStyle3(cell.html_style) : {},
18446
+ ...isMaximized ? { gridColumn: "1 / -1" } : {},
18447
+ ...isMinimized ? { minHeight: 0 } : {}
18448
+ };
18449
+ const toolbarStyle = {
18450
+ display: "flex",
18451
+ alignItems: "center",
18452
+ justifyContent: "space-between",
18453
+ padding: "2px 8px",
18454
+ gap: 2,
18455
+ borderBottom: `1px solid ${token.colorBorderSecondary}`,
18456
+ background: token.colorBgContainer,
18457
+ flexShrink: 0,
18458
+ minHeight: 32,
18459
+ position: "relative"
18460
+ };
18461
+ const resource = model?.resource || cell.model;
18462
+ const cellTitle = model?.label || cell.model;
18463
+ return /* @__PURE__ */ jsxs("div", { style: cellStyle, className: "jm-dashboard-cell", children: [
18464
+ /* @__PURE__ */ jsx("style", { children: `
18465
+ .jm-dashboard-cell .jm-cell-actions { opacity: 0; transition: opacity 0.15s; }
18466
+ .jm-dashboard-cell:hover .jm-cell-actions { opacity: 1; }
18467
+ ` }),
18468
+ /* @__PURE__ */ jsxs("div", { style: toolbarStyle, children: [
18469
+ /* @__PURE__ */ jsx("span", { style: {
18470
+ fontSize: token.fontSizeSM,
18471
+ fontWeight: token.fontWeightStrong,
18472
+ color: token.colorText,
18473
+ paddingLeft: 4,
18474
+ overflow: "hidden",
18475
+ textOverflow: "ellipsis",
18476
+ whiteSpace: "nowrap"
18477
+ }, children: cellTitle }),
18478
+ /* @__PURE__ */ jsxs("div", { className: "jm-cell-actions", style: { display: "flex", alignItems: "center", gap: 2 }, children: [
18479
+ /* @__PURE__ */ jsx(Tooltip, { title: "Configure cell", children: /* @__PURE__ */ jsx(
18480
+ Button,
18481
+ {
18482
+ type: "text",
18483
+ size: "small",
18484
+ icon: /* @__PURE__ */ jsx(SettingOutlined, { style: { fontSize: 11 } }),
18485
+ onClick: onConfigure,
18486
+ style: { color: token.colorTextTertiary, padding: "0 4px", height: 22, minWidth: 22 }
18487
+ }
18488
+ ) }),
18489
+ /* @__PURE__ */ jsx(Tooltip, { title: "Open full page", children: /* @__PURE__ */ jsx(Link, { to: `/${resource}`, style: { color: token.colorTextTertiary, display: "flex", alignItems: "center", padding: "0 4px" }, children: /* @__PURE__ */ jsx(LinkOutlined, { style: { fontSize: 11 } }) }) }),
18490
+ /* @__PURE__ */ jsx(Tooltip, { title: isMaximized ? "Restore" : "Maximize", children: /* @__PURE__ */ jsx(
18491
+ Button,
18492
+ {
18493
+ type: "text",
18494
+ size: "small",
18495
+ icon: /* @__PURE__ */ jsx(FullscreenOutlined, { style: { fontSize: 11 } }),
18496
+ onClick: onMaximize,
18497
+ style: { color: token.colorTextTertiary, padding: "0 4px", height: 22, minWidth: 22 }
18498
+ }
18499
+ ) }),
18500
+ /* @__PURE__ */ jsx(Tooltip, { title: isMinimized ? "Restore" : "Minimize", children: /* @__PURE__ */ jsx(
18501
+ Button,
18502
+ {
18503
+ type: "text",
18504
+ size: "small",
18505
+ icon: /* @__PURE__ */ jsx(MinusSquareOutlined, { style: { fontSize: 11 } }),
18506
+ onClick: onMinimize,
18507
+ style: { color: token.colorTextTertiary, padding: "0 4px", height: 22, minWidth: 22 }
18508
+ }
18509
+ ) })
18510
+ ] })
18511
+ ] }),
18512
+ !isMinimized && /* @__PURE__ */ jsx("div", { style: { flex: 1, overflow: "auto", minHeight: 0 }, children: model ? /* @__PURE__ */ jsx(
18513
+ DynamicList,
18514
+ {
18515
+ model,
18516
+ allModels,
18517
+ isEmbedded: true,
18518
+ preferencesResourceOverride: `dashboard:${resource}`,
18519
+ defaultListVisible: false,
18520
+ listViewType: cell.view_type ? cell.view_type : model.listViewType
18521
+ }
18522
+ ) : /* @__PURE__ */ jsx(
18523
+ Empty,
18524
+ {
18525
+ description: `Model "${cell.model}" not found`,
18526
+ style: { padding: 24 },
18527
+ image: Empty.PRESENTED_IMAGE_SIMPLE
18528
+ }
18529
+ ) })
18530
+ ] });
18531
+ };
18532
+ var DashboardTabContent = ({ tab, allModels, maximizedCellId, minimizedCellIds, onMaximize, onMinimize, onConfigure }) => {
18533
+ const cells = tab.cells;
18534
+ const numCols = useMemo(() => {
18535
+ if (!cells.length) return 2;
18536
+ return Math.max(...cells.map((c) => c.col)) + 1;
18537
+ }, [cells]);
18538
+ const numRows = useMemo(() => {
18539
+ if (!cells.length) return 1;
18540
+ return Math.max(...cells.map((c) => c.row)) + 1;
18541
+ }, [cells]);
18542
+ const visibleCells = maximizedCellId ? cells.filter((c) => c.id === maximizedCellId) : cells;
18543
+ const gridStyle = {
18544
+ display: "grid",
18545
+ gridTemplateColumns: maximizedCellId ? "1fr" : `repeat(${numCols}, 1fr)`,
18546
+ gridTemplateRows: maximizedCellId ? "1fr" : `repeat(${numRows}, minmax(320px, auto))`,
18547
+ gap: 12,
18548
+ padding: 12,
18549
+ height: "100%",
18550
+ boxSizing: "border-box"
18551
+ };
18552
+ if (!cells.length) {
18553
+ return /* @__PURE__ */ jsx(Empty, { description: "No models in this tab", style: { padding: 48 } });
18554
+ }
18555
+ return /* @__PURE__ */ jsx("div", { style: gridStyle, children: visibleCells.map((cell) => /* @__PURE__ */ jsx(
18556
+ "div",
18557
+ {
18558
+ style: {
18559
+ gridColumn: maximizedCellId ? "1 / -1" : `${cell.col + 1}`,
18560
+ gridRow: maximizedCellId ? "1 / -1" : `${cell.row + 1}`
18561
+ },
18562
+ children: /* @__PURE__ */ jsx(
18563
+ DashboardGridCell,
18564
+ {
18565
+ cell,
18566
+ allModels,
18567
+ isMaximized: maximizedCellId === cell.id,
18568
+ isMinimized: minimizedCellIds.has(cell.id),
18569
+ onConfigure: () => onConfigure(cell),
18570
+ onMaximize: () => onMaximize(cell.id),
18571
+ onMinimize: () => onMinimize(cell.id)
18572
+ }
18573
+ )
18574
+ },
18575
+ cell.id
18576
+ )) });
18577
+ };
18578
+ var ViewsGrid = ({ config, allModels, onConfigChange }) => {
18579
+ const [maximizedCellId, setMaximizedCellId] = useState(null);
18580
+ const [minimizedCellIds, setMinimizedCellIds] = useState(/* @__PURE__ */ new Set());
18581
+ const [drawerSelection, setDrawerSelection] = useState(null);
18582
+ const handleMaximize = useCallback((cellId) => {
18583
+ setMaximizedCellId((prev) => prev === cellId ? null : cellId);
18584
+ }, []);
18585
+ const handleMinimize = useCallback((cellId) => {
18586
+ setMinimizedCellIds((prev) => {
18587
+ const next = new Set(prev);
18588
+ if (next.has(cellId)) {
18589
+ next.delete(cellId);
18590
+ } else {
18591
+ next.add(cellId);
18592
+ }
18593
+ return next;
18594
+ });
18595
+ }, []);
18596
+ const handleOpenDrawer = useCallback((tabId, cell) => {
18597
+ setDrawerSelection({ tabId, cell });
18598
+ }, []);
18599
+ const handleSaveConfig = useCallback((nextConfig) => {
18600
+ onConfigChange(nextConfig);
18601
+ setDrawerSelection(null);
18602
+ }, [onConfigChange]);
18603
+ const tabItems = useMemo(
18604
+ () => config.tabs.map((tab) => ({
18605
+ key: tab.id,
18606
+ label: tab.name,
18607
+ children: /* @__PURE__ */ jsx(
18608
+ DashboardTabContent,
18609
+ {
18610
+ tab,
18611
+ allModels,
18612
+ maximizedCellId,
18613
+ minimizedCellIds,
18614
+ onMaximize: handleMaximize,
18615
+ onMinimize: handleMinimize,
18616
+ onConfigure: (cell) => handleOpenDrawer(tab.id, cell)
18617
+ }
18618
+ )
18619
+ })),
18620
+ [config.tabs, allModels, maximizedCellId, minimizedCellIds, handleMaximize, handleMinimize, handleOpenDrawer]
18621
+ );
18622
+ if (!config.tabs.length) {
18623
+ return /* @__PURE__ */ jsx(Empty, { description: "No tabs configured. Run veloiq add-dashboard to add models.", style: { padding: 48 } });
18624
+ }
18625
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
18626
+ /* @__PURE__ */ jsx(
18627
+ Tabs,
18628
+ {
18629
+ items: tabItems,
18630
+ onChange: () => {
18631
+ setMaximizedCellId(null);
18632
+ setMinimizedCellIds(/* @__PURE__ */ new Set());
18633
+ },
18634
+ style: { height: "100%" },
18635
+ tabBarStyle: { paddingLeft: 12, marginBottom: 0 }
18636
+ }
18637
+ ),
18638
+ /* @__PURE__ */ jsx(
18639
+ CellConfigDrawer,
18640
+ {
18641
+ open: Boolean(drawerSelection),
18642
+ cell: drawerSelection?.cell ?? null,
18643
+ tabId: drawerSelection?.tabId ?? null,
18644
+ config,
18645
+ onClose: () => setDrawerSelection(null),
18646
+ onSave: handleSaveConfig
18647
+ }
18648
+ )
18649
+ ] });
18650
+ };
18651
+ function parseInlineStyle3(cssText) {
18652
+ const result = {};
18653
+ cssText.split(";").forEach((declaration) => {
18654
+ const idx = declaration.indexOf(":");
18655
+ if (idx < 0) return;
18656
+ const prop = declaration.slice(0, idx).trim();
18657
+ const value = declaration.slice(idx + 1).trim();
18658
+ if (!prop || !value) return;
18659
+ const camel = prop.replace(/-([a-z])/g, (_39, c) => c.toUpperCase());
18660
+ result[camel] = value;
18661
+ });
18662
+ return result;
18663
+ }
18664
+ function useRecentActivity(days) {
18665
+ const [data, setData] = useState(null);
18666
+ const [loading, setLoading] = useState(true);
18667
+ const load = useCallback(async () => {
18668
+ setLoading(true);
18669
+ try {
18670
+ const params = days !== void 0 ? `?days=${days}` : "";
18671
+ const res = await authenticatedFetch(`${API_URL3}/dashboard/recent-activity${params}`);
18672
+ if (res.ok) setData(await res.json());
18673
+ } catch {
18674
+ } finally {
18675
+ setLoading(false);
18676
+ }
18677
+ }, [days]);
18678
+ useEffect(() => {
18679
+ load();
18680
+ }, [load]);
18681
+ return { data, loading, reload: load };
18682
+ }
18683
+ var { Text: Text2, Title: Title9 } = Typography;
18684
+ function relativeTime(iso) {
18685
+ if (!iso) return "";
18686
+ const diff = Date.now() - new Date(iso).getTime();
18687
+ const mins = Math.floor(diff / 6e4);
18688
+ if (mins < 1) return "just now";
18689
+ if (mins < 60) return `${mins}m ago`;
18690
+ const hrs = Math.floor(mins / 60);
18691
+ if (hrs < 24) return `${hrs}h ago`;
18692
+ const days = Math.floor(hrs / 24);
18693
+ if (days < 30) return `${days}d ago`;
18694
+ return new Date(iso).toLocaleDateString();
18695
+ }
18696
+ var RecentActivityPanel = () => {
18697
+ const { token } = theme.useToken();
18698
+ const allModels = useAllModels();
18699
+ const [days, setDays] = useState(30);
18700
+ const { data, loading, reload } = useRecentActivity(days);
18701
+ const groups = data?.groups ?? [];
18702
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "16px 0" }, children: [
18703
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, marginBottom: 20, paddingLeft: 4 }, children: [
18704
+ /* @__PURE__ */ jsx(Text2, { type: "secondary", children: "Show activity from the last" }),
18705
+ /* @__PURE__ */ jsx(
18706
+ InputNumber,
18707
+ {
18708
+ min: 1,
18709
+ max: 365,
18710
+ value: days,
18711
+ onChange: (v) => v && setDays(v),
18712
+ style: { width: 72 },
18713
+ size: "small"
18714
+ }
18715
+ ),
18716
+ /* @__PURE__ */ jsx(Text2, { type: "secondary", children: "days" }),
18717
+ /* @__PURE__ */ jsx(Tooltip, { title: "Refresh", children: /* @__PURE__ */ jsx(
18718
+ ReloadOutlined,
18719
+ {
18720
+ style: { color: token.colorTextTertiary, cursor: "pointer", fontSize: 13 },
18721
+ onClick: reload
18722
+ }
18723
+ ) }),
18724
+ data && /* @__PURE__ */ jsxs(Text2, { type: "secondary", style: { fontSize: 12 }, children: [
18725
+ groups.reduce((n, g) => n + g.records.length, 0),
18726
+ " records across ",
18727
+ groups.length,
18728
+ " models"
18729
+ ] })
18730
+ ] }),
18731
+ loading ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: 48 }, children: /* @__PURE__ */ jsx(Spin, {}) }) : groups.length === 0 ? /* @__PURE__ */ jsx(
18732
+ Empty,
18733
+ {
18734
+ description: `No activity in the last ${days} days`,
18735
+ image: Empty.PRESENTED_IMAGE_SIMPLE,
18736
+ style: { padding: 48 }
18737
+ }
18738
+ ) : /* @__PURE__ */ jsx(Space, { direction: "vertical", size: 24, style: { width: "100%" }, children: groups.map((group) => {
18739
+ const model = findModelByName(allModels, group.resource);
18740
+ const tone = getModelTone(model?.name ?? group.resource);
18741
+ const label = model?.label ?? group.model_name;
18742
+ return /* @__PURE__ */ jsxs("div", { children: [
18743
+ /* @__PURE__ */ jsxs("div", { style: {
18744
+ display: "flex",
18745
+ alignItems: "center",
18746
+ gap: 8,
18747
+ marginBottom: 6,
18748
+ paddingBottom: 6,
18749
+ borderBottom: `2px solid ${tone.solid}40`
18750
+ }, children: [
18751
+ /* @__PURE__ */ jsx("div", { style: {
18752
+ width: 10,
18753
+ height: 10,
18754
+ borderRadius: "50%",
18755
+ background: tone.solid,
18756
+ flexShrink: 0
18757
+ } }),
18758
+ /* @__PURE__ */ jsx(Title9, { level: 5, style: { margin: 0, color: tone.text }, children: label }),
18759
+ /* @__PURE__ */ jsx(Tag, { color: tone.solid, style: { marginLeft: "auto", fontSize: 11 }, children: group.records.length })
18760
+ ] }),
18761
+ /* @__PURE__ */ jsx(
18762
+ List$1,
18763
+ {
18764
+ size: "small",
18765
+ dataSource: group.records,
18766
+ renderItem: (rec) => {
18767
+ const timestamp = rec.updated_at || rec.created_at;
18768
+ const isNew = rec.created_at === rec.updated_at;
18769
+ return /* @__PURE__ */ jsxs(
18770
+ List$1.Item,
18771
+ {
18772
+ style: {
18773
+ padding: "4px 8px",
18774
+ borderRadius: token.borderRadius,
18775
+ transition: "background 0.15s"
18776
+ },
18777
+ className: "jm-activity-row",
18778
+ children: [
18779
+ /* @__PURE__ */ jsx("style", { children: `
18780
+ .jm-activity-row:hover { background: ${token.colorFillAlter}; }
18781
+ ` }),
18782
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, width: "100%" }, children: [
18783
+ /* @__PURE__ */ jsx(ClockCircleOutlined, { style: { color: token.colorTextTertiary, fontSize: 11, flexShrink: 0 } }),
18784
+ /* @__PURE__ */ jsx(
18785
+ Link,
18786
+ {
18787
+ to: `/${group.resource}/show/${rec.id}`,
18788
+ style: { flex: 1, color: token.colorText, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
18789
+ children: rec._label || `#${rec.id}`
18790
+ }
18791
+ ),
18792
+ isNew && /* @__PURE__ */ jsx(Tag, { color: "green", style: { fontSize: 10, padding: "0 4px", lineHeight: "16px" }, children: "new" }),
18793
+ /* @__PURE__ */ jsx(Text2, { type: "secondary", style: { fontSize: 11, flexShrink: 0 }, children: relativeTime(timestamp) })
18794
+ ] })
18795
+ ]
18796
+ }
18797
+ );
18798
+ }
18799
+ }
18800
+ )
18801
+ ] }, group.resource);
18802
+ }) })
18803
+ ] });
18804
+ };
18805
+ var { Text: AntText, Title: AntTitle } = Typography;
18806
+ function usePinnedRecords() {
18807
+ const [groups, setGroups] = useState([]);
18808
+ const [loading, setLoading] = useState(true);
18809
+ const load = useCallback(async () => {
18810
+ setLoading(true);
18811
+ try {
18812
+ const res = await authenticatedFetch(`${API_URL3}/dashboard/pinned-records`);
18813
+ if (res.ok) {
18814
+ const data = await res.json();
18815
+ setGroups(data.groups ?? []);
18816
+ }
18817
+ } catch {
18818
+ } finally {
18819
+ setLoading(false);
18820
+ }
18821
+ }, []);
18822
+ React6.useEffect(() => {
18823
+ load();
18824
+ }, [load]);
18825
+ return { groups, loading, reload: load };
18826
+ }
18827
+ var PinnedRecordsPanel = () => {
18828
+ const { token } = theme.useToken();
18829
+ const allModels = useAllModels();
18830
+ const { groups, loading, reload } = usePinnedRecords();
18831
+ const [unpinning, setUnpinning] = useState(/* @__PURE__ */ new Set());
18832
+ const visibleGroups = groups.filter((g) => findModelByName(allModels, g.resource));
18833
+ const handleUnpin = useCallback(async (resource, recordId) => {
18834
+ const key = `${resource}:${recordId}`;
18835
+ setUnpinning((prev) => new Set(prev).add(key));
18836
+ try {
18837
+ await unpinRecords(resource, [recordId]);
18838
+ await reload();
18839
+ } finally {
18840
+ setUnpinning((prev) => {
18841
+ const next = new Set(prev);
18842
+ next.delete(key);
18843
+ return next;
18844
+ });
18845
+ }
18846
+ }, [reload]);
18847
+ return /* @__PURE__ */ jsxs("div", { style: { padding: "16px 0" }, children: [
18848
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, marginBottom: 20, paddingLeft: 4 }, children: [
18849
+ /* @__PURE__ */ jsx(PushpinFilled, { style: { color: "#faad14", fontSize: 14 } }),
18850
+ /* @__PURE__ */ jsx(AntText, { type: "secondary", children: "Records you've pinned across the app" }),
18851
+ /* @__PURE__ */ jsx(Tooltip, { title: "Refresh", children: /* @__PURE__ */ jsx(
18852
+ ReloadOutlined,
18853
+ {
18854
+ style: { color: token.colorTextTertiary, cursor: "pointer", fontSize: 13 },
18855
+ onClick: reload
18856
+ }
18857
+ ) }),
18858
+ !loading && /* @__PURE__ */ jsxs(AntText, { type: "secondary", style: { fontSize: 12 }, children: [
18859
+ visibleGroups.reduce((n, g) => n + g.records.length, 0),
18860
+ " pins across ",
18861
+ visibleGroups.length,
18862
+ " models"
18863
+ ] })
18864
+ ] }),
18865
+ loading ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: 48 }, children: /* @__PURE__ */ jsx(Spin, {}) }) : visibleGroups.length === 0 ? /* @__PURE__ */ jsx(
18866
+ Empty,
18867
+ {
18868
+ description: /* @__PURE__ */ jsxs("span", { children: [
18869
+ "No pinned records yet.",
18870
+ /* @__PURE__ */ jsx("br", {}),
18871
+ /* @__PURE__ */ jsxs(AntText, { type: "secondary", style: { fontSize: 12 }, children: [
18872
+ "Open any record and click the ",
18873
+ /* @__PURE__ */ jsx(PushpinOutlined, {}),
18874
+ " pin button to pin it here."
18875
+ ] })
18876
+ ] }),
18877
+ image: Empty.PRESENTED_IMAGE_SIMPLE,
18878
+ style: { padding: 48 }
18879
+ }
18880
+ ) : /* @__PURE__ */ jsx(Space, { direction: "vertical", size: 24, style: { width: "100%" }, children: visibleGroups.map((group) => {
18881
+ const model = findModelByName(allModels, group.resource);
18882
+ const tone = getModelTone(model?.name ?? group.resource);
18883
+ const label = model?.label ?? group.model_name;
18884
+ return /* @__PURE__ */ jsxs("div", { children: [
18885
+ /* @__PURE__ */ jsxs("div", { style: {
18886
+ display: "flex",
18887
+ alignItems: "center",
18888
+ gap: 8,
18889
+ marginBottom: 6,
18890
+ paddingBottom: 6,
18891
+ borderBottom: `2px solid ${tone.solid}40`
18892
+ }, children: [
18893
+ /* @__PURE__ */ jsx("div", { style: {
18894
+ width: 10,
18895
+ height: 10,
18896
+ borderRadius: "50%",
18897
+ background: tone.solid,
18898
+ flexShrink: 0
18899
+ } }),
18900
+ /* @__PURE__ */ jsx(AntTitle, { level: 5, style: { margin: 0, color: tone.text }, children: label }),
18901
+ /* @__PURE__ */ jsx(Tag, { color: tone.solid, style: { marginLeft: "auto", fontSize: 11 }, children: group.records.length })
18902
+ ] }),
18903
+ /* @__PURE__ */ jsx(
18904
+ List$1,
18905
+ {
18906
+ size: "small",
18907
+ dataSource: group.records,
18908
+ renderItem: (rec) => {
18909
+ const key = `${group.resource}:${rec.id}`;
18910
+ return /* @__PURE__ */ jsxs(
18911
+ List$1.Item,
18912
+ {
18913
+ style: {
18914
+ padding: "4px 8px",
18915
+ borderRadius: token.borderRadius,
18916
+ transition: "background 0.15s"
18917
+ },
18918
+ className: "jm-pin-row",
18919
+ children: [
18920
+ /* @__PURE__ */ jsx("style", { children: `
18921
+ .jm-pin-row:hover { background: ${token.colorFillAlter}; }
18922
+ .jm-pin-row .jm-unpin-btn { opacity: 0; transition: opacity 0.15s; }
18923
+ .jm-pin-row:hover .jm-unpin-btn { opacity: 1; }
18924
+ ` }),
18925
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, width: "100%" }, children: [
18926
+ /* @__PURE__ */ jsx(PushpinFilled, { style: { color: "#faad14", fontSize: 11, flexShrink: 0 } }),
18927
+ /* @__PURE__ */ jsx(
18928
+ Link,
18929
+ {
18930
+ to: `/${group.resource}/show/${rec.id}`,
18931
+ style: { flex: 1, color: token.colorText, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
18932
+ children: rec._label || `#${rec.id}`
18933
+ }
18934
+ ),
18935
+ /* @__PURE__ */ jsx(Tooltip, { title: "Unpin", children: /* @__PURE__ */ jsx(
18936
+ Button,
18937
+ {
18938
+ className: "jm-unpin-btn",
18939
+ type: "text",
18940
+ size: "small",
18941
+ icon: /* @__PURE__ */ jsx(PushpinFilled, { style: { color: "#faad14" } }),
18942
+ loading: unpinning.has(key),
18943
+ onClick: () => handleUnpin(group.resource, rec.id),
18944
+ style: { color: token.colorTextTertiary, height: 20, minWidth: 20, padding: "0 4px" }
18945
+ }
18946
+ ) })
18947
+ ] })
18948
+ ]
18949
+ }
18950
+ );
18951
+ }
18952
+ }
18953
+ )
18954
+ ] }, group.resource);
18955
+ }) })
18956
+ ] });
18957
+ };
18958
+ var { Text: Text3 } = Typography;
18959
+ var _38 = window._ || ((text) => text);
18960
+ var DashboardPage = () => {
18961
+ const { token } = theme.useToken();
18962
+ const allModels = useAllModels();
18963
+ const { config, enabled, loading, save } = useDashboardConfig();
18964
+ if (loading) {
18965
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: 64 }, children: /* @__PURE__ */ jsx(Spin, {}) });
18966
+ }
18967
+ if (!enabled || !config) {
18968
+ return /* @__PURE__ */ jsx("div", { style: { padding: 48 }, children: /* @__PURE__ */ jsx(
18969
+ Empty,
18970
+ {
18971
+ image: /* @__PURE__ */ jsx(DashboardOutlined, { style: { fontSize: 48, color: token.colorTextTertiary } }),
18972
+ imageStyle: { height: 60 },
18973
+ description: /* @__PURE__ */ jsxs("span", { children: [
18974
+ "No dashboard configured.",
18975
+ /* @__PURE__ */ jsx("br", {}),
18976
+ /* @__PURE__ */ jsxs(Text3, { type: "secondary", children: [
18977
+ "Run ",
18978
+ /* @__PURE__ */ jsx("code", { children: "veloiq add-dashboard <model> \u2026" }),
18979
+ " to get started."
18980
+ ] })
18981
+ ] })
18982
+ }
18983
+ ) });
18984
+ }
18985
+ const tabs = [
18986
+ {
18987
+ key: "models_grid",
18988
+ label: _38("Models Grid"),
18989
+ children: /* @__PURE__ */ jsx("div", { style: { height: "calc(100vh - 140px)", overflow: "auto" }, children: /* @__PURE__ */ jsx(
18990
+ ViewsGrid,
18991
+ {
18992
+ config,
18993
+ allModels,
18994
+ onConfigChange: save
18995
+ }
18996
+ ) })
18997
+ },
18998
+ {
18999
+ key: "recent_activity",
19000
+ label: _38("Recent Activity"),
19001
+ children: /* @__PURE__ */ jsx("div", { style: { height: "calc(100vh - 140px)", overflow: "auto", padding: "0 12px" }, children: /* @__PURE__ */ jsx(RecentActivityPanel, {}) })
19002
+ },
19003
+ {
19004
+ key: "pinned_records",
19005
+ label: _38("Pinned Records"),
19006
+ children: /* @__PURE__ */ jsx("div", { style: { height: "calc(100vh - 140px)", overflow: "auto", padding: "0 12px" }, children: /* @__PURE__ */ jsx(PinnedRecordsPanel, {}) })
19007
+ }
19008
+ ];
19009
+ return /* @__PURE__ */ jsx("div", { style: { padding: "0 16px", height: "100%" }, children: /* @__PURE__ */ jsx(
19010
+ Tabs,
19011
+ {
19012
+ items: tabs,
19013
+ tabBarStyle: { marginBottom: 0 }
19014
+ }
19015
+ ) });
19016
+ };
18165
19017
 
18166
19018
  // src/utils/generateResources.ts
18167
19019
  function generateResources(models, moduleName, options = {}) {
@@ -18305,6 +19157,6 @@ var authSystemModels = [
18305
19157
  }
18306
19158
  ];
18307
19159
 
18308
- export { API_URL3 as API_URL, AllModelsProvider, ColorModeContext, ColorModeContextProvider, CustomSider, DynamicCreate, DynamicEdit, DynamicList, DynamicShow, ExecutableHtml, GlobalSearch, HierarchyView, HorizontalMenu, InlinePlotlyHtml, LayoutWrapper, LoginPage, ModelHeading, MultiPaneLayout, PaneNavigationContext, PrimaryShowContext, ReferenceField, ResourceContext, ShowFooterButtons, StandardList, StandardShow, accessControlProvider, authProvider, authSystemModels, authenticatedFetch, buildShowTabFormOptions, generateResources, getModelTone, httpClient, normalizeToneKey, renderRelationBlock, setColorSchemas, useAllModels, useKeyboardShortcuts, useMetadataModal, usePaneNavigation, useShowActionsPreferences, useShowEditableForm, useStandardShowTabs };
19160
+ export { API_URL3 as API_URL, AllModelsProvider, ColorModeContext, ColorModeContextProvider, CustomSider, DashboardPage, DynamicCreate, DynamicEdit, DynamicList, DynamicShow, ExecutableHtml, GlobalSearch, HierarchyView, HorizontalMenu, InlinePlotlyHtml, LayoutWrapper, LoginPage, ModelHeading, MultiPaneLayout, PaneNavigationContext, PinnedRecordsPanel, PrimaryShowContext, RecentActivityPanel, ReferenceField, ResourceContext, ShowFooterButtons, StandardList, StandardShow, ViewsGrid, accessControlProvider, authProvider, authSystemModels, authenticatedFetch, buildShowTabFormOptions, generateResources, getModelTone, httpClient, normalizeToneKey, renderRelationBlock, setColorSchemas, useAllModels, useKeyboardShortcuts, useMetadataModal, usePaneNavigation, useShowActionsPreferences, useShowEditableForm, useStandardShowTabs };
18309
19161
  //# sourceMappingURL=index.mjs.map
18310
19162
  //# sourceMappingURL=index.mjs.map