@ramesesinc/platform-core 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { compile } from "expression-eval";
2
3
  import { useEffect, useState } from "react";
3
4
  import { usePageContext } from "../../core/PageContext";
4
5
  const UIComponent = (props) => {
@@ -20,7 +21,8 @@ const UIComponent = (props) => {
20
21
  return JSON.stringify(val !== null && val !== void 0 ? val : null);
21
22
  });
22
23
  // eslint-disable-next-line no-new-func
23
- return Boolean(new Function(`return (${jsExpr})`)());
24
+ // return Boolean(new Function(`return (${jsExpr})`)());
25
+ return Boolean(compile(jsExpr)(context));
24
26
  }
25
27
  catch (e) {
26
28
  console.warn("visibleWhen eval error:", e);
@@ -1,3 +1,4 @@
1
+ import { compile } from "expression-eval";
1
2
  import { useEffect, useState } from "react";
2
3
  import { usePageContext } from "../../core/PageContext";
3
4
 
@@ -28,7 +29,8 @@ const UIComponent = (props: UIComponentProps) => {
28
29
  return JSON.stringify(val ?? null);
29
30
  });
30
31
  // eslint-disable-next-line no-new-func
31
- return Boolean(new Function(`return (${jsExpr})`)());
32
+ // return Boolean(new Function(`return (${jsExpr})`)());
33
+ return Boolean(compile(jsExpr)(context));
32
34
  } catch (e) {
33
35
  console.warn("visibleWhen eval error:", e);
34
36
  return true;
@@ -1,5 +1,4 @@
1
1
  type LabelProps = {
2
- label?: string;
3
2
  depends?: string;
4
3
  expr?: string;
5
4
  html?: boolean;
@@ -5,7 +5,6 @@ import { render } from "../../lib/utils/ExprUtil";
5
5
  import UIComponent from "../common/UIComponent";
6
6
 
7
7
  type LabelProps = {
8
- label?: string;
9
8
  depends?: string;
10
9
  expr?: string;
11
10
  html?: boolean;
@@ -80,9 +80,13 @@ export interface DataListAttr {
80
80
  className?: string;
81
81
  rowClassName?: (row: any, rowIndex: number) => string;
82
82
  depends?: string;
83
+ handle?: InnerDataListHandle;
83
84
  }
84
85
  export interface DataListProps {
85
86
  attr: DataListAttr;
86
87
  }
88
+ type InnerDataListHandle = {
89
+ init: (ref: any) => void;
90
+ };
87
91
  export declare const DataList: React.FC<DataListProps>;
88
92
  export default DataList;
@@ -11,8 +11,9 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { Columns, Eye, RefreshCcw, Search, Trash } from "lucide-react";
14
- import { useCallback, useState } from "react";
14
+ import { useCallback, useRef, useState } from "react";
15
15
  import { useApp } from "../../core/AppContext";
16
+ import { useDataContext } from "../../core/DataContext";
16
17
  import { DynamicComponent } from "../../core/DynamicComponent";
17
18
  import { usePageContext } from "../../core/PageContext";
18
19
  import Panel from "../../core/Panel";
@@ -22,7 +23,7 @@ import { getUrlPageParams } from "../../lib/utils/PageUtils";
22
23
  import { DataTable } from "./DataTable";
23
24
  import ListHandler from "./ListHandler";
24
25
  import { TableProvider, useTableContext } from "./TableContext";
25
- const InnerDataList = ({ cols, emptyMessage = "No data available", errorMessage, striped = false, bordered = false, hover = true, dense = false, showPagination = true, paginationPosition = "bottom", showPageInfo = true, showTotalCount = true, showRowsPerPage = true, rowsPerPageOptions = [5, 10, 20, 50, 100], searchable = false, searchPlaceholder = "Search...", searchDebounce = 300, onSearchChange, filters = [], showFilterPanel = false, onFilterChange, sortable = true, showSortIndicator = true, selectable = false, selectionMode = "multiple", onSelectionChange, selectOnRowClick = false, onRowClick, rowActions = [], bulkActions = [], showBulkActions = true, toolbarActions = [], showToolbar = true, toolbarTitle, showRefreshButton = true, showExportButton = false, onLoad, onError, onRefresh, onExport, className = "", rowClassName, depends, }) => {
26
+ const InnerDataList = ({ cols, emptyMessage = "No data available", errorMessage, striped = false, bordered = false, hover = true, dense = false, showPagination = true, paginationPosition = "bottom", showPageInfo = true, showTotalCount = true, showRowsPerPage = true, rowsPerPageOptions = [5, 10, 20, 50, 100], searchable = false, searchPlaceholder = "Search...", searchDebounce = 300, onSearchChange, filters = [], showFilterPanel = false, onFilterChange, sortable = true, showSortIndicator = true, selectable = false, selectionMode = "multiple", onSelectionChange, selectOnRowClick = false, onRowClick, rowActions = [], bulkActions = [], showBulkActions = true, toolbarActions = [], showToolbar = true, toolbarTitle, showRefreshButton = true, showExportButton = false, onLoad, onError, onRefresh, onExport, className = "", rowClassName, depends, handle, }) => {
26
27
  const { listHandler, columns, rows, setRows, loading: ctxLoading, setLoading } = useTableContext();
27
28
  // ============================================================================
28
29
  // STATE
@@ -37,6 +38,17 @@ const InnerDataList = ({ cols, emptyMessage = "No data available", errorMessage,
37
38
  const [hiddenColumns, setHiddenColumns] = useState([]);
38
39
  const isLoading = ctxLoading || internalLoading;
39
40
  const pageContext = usePageContext();
41
+ const handleRef = {
42
+ setFilter: async (filter) => {
43
+ if (filter == null || listHandler == null)
44
+ return;
45
+ await listHandler.setFilter(filter);
46
+ await loadData();
47
+ },
48
+ };
49
+ if (handle != null) {
50
+ handle.init(handleRef);
51
+ }
40
52
  const refreshHandler = async () => {
41
53
  // console.log("refresh via notify depends", pageContext?.uuid);
42
54
  await handleRefresh();
@@ -284,18 +296,12 @@ const InnerDataList = ({ cols, emptyMessage = "No data available", errorMessage,
284
296
  // ============================================================================
285
297
  return (_jsxs("div", { className: `data-list ${className}`, children: [renderToolbar(), renderFilterPanel(), (paginationPosition === "both" || paginationPosition === "top") && renderPagination(), _jsx(DataTable, { data: rows, columns: sortableColumns, loading: isLoading, emptyMessage: emptyMessage, striped: striped, bordered: bordered, hover: hover, dense: dense, rowKey: "id", onRowClick: handleRowClick, selectedRows: selectedRows, onSelectionChange: handleSelectionChange, selectable: selectable, rowClassName: rowClassName }), (paginationPosition === "both" || paginationPosition === "bottom") && rows.length > 0 && renderPagination()] }));
286
298
  };
287
- // ============================================================================
288
- // PUBLIC DATALIST
289
- // - Only accepts attr
290
- // - All hooks (useApp, usePageContext) called here
291
- // - factory defined here
292
- // - resolves dynamic filter params before passing to TableProvider
293
- // ============================================================================
294
299
  export const DataList = ({ attr }) => {
295
- const { cols, data, rowsPerPage, disableTotalCount, commonActions, rowActions, toolbarActions } = attr, rest = __rest(attr, ["cols", "data", "rowsPerPage", "disableTotalCount", "commonActions", "rowActions", "toolbarActions"]);
300
+ const { depends, cols, data, rowsPerPage, disableTotalCount, commonActions, rowActions, toolbarActions } = attr, rest = __rest(attr, ["depends", "cols", "data", "rowsPerPage", "disableTotalCount", "commonActions", "rowActions", "toolbarActions"]);
296
301
  // All hooks called here — this is a React component so hooks are valid
297
302
  const { tenant, module } = useApp();
298
303
  const pageContext = usePageContext();
304
+ const dataContext = useDataContext();
299
305
  // Factory defined here — ListHandler is a local import, no prop needed
300
306
  const listHandlerFactory = (config) => ListHandler(config);
301
307
  // Resolve dynamic placeholders in filter before ListHandler is constructed.
@@ -335,10 +341,22 @@ export const DataList = ({ attr }) => {
335
341
  newToolbarActions.push({
336
342
  label: "Filter",
337
343
  component: "LookupPage",
338
- attr: Object.assign(Object.assign({}, commonActions.filterPage), { title: "Filter" }),
344
+ attr: Object.assign(Object.assign({}, commonActions.filterPage), { name: "customFilter", title: "Filter" }),
339
345
  });
340
346
  }
341
- return (_jsx(TableProvider, { data: data, columns: cols, rowsPerPage: rowsPerPage, disableTotalCount: disableTotalCount, listHandlerFactory: listHandlerFactory, tenant: tenant !== null && tenant !== void 0 ? tenant : "", module: module !== null && module !== void 0 ? module : "", resolvedParams: resolveParams(), children: _jsx(InnerDataList, Object.assign({}, rest, { cols: cols, rowActions: newRowActions, toolbarActions: newToolbarActions })) }));
347
+ const handleRef = useRef(null);
348
+ const onRefresh = () => {
349
+ var _a;
350
+ const filter = dataContext === null || dataContext === void 0 ? void 0 : dataContext.get("customFilter");
351
+ (_a = handleRef.current) === null || _a === void 0 ? void 0 : _a.setFilter(filter !== null && filter !== void 0 ? filter : {});
352
+ };
353
+ useDependHandler({ name: "customFilter", onRefresh });
354
+ const innerHandle = {
355
+ init: (ref) => {
356
+ handleRef.current = ref;
357
+ },
358
+ };
359
+ return (_jsx(TableProvider, { data: data, columns: cols, rowsPerPage: rowsPerPage, disableTotalCount: disableTotalCount, listHandlerFactory: listHandlerFactory, tenant: tenant !== null && tenant !== void 0 ? tenant : "", module: module !== null && module !== void 0 ? module : "", resolvedParams: resolveParams(), children: _jsx(InnerDataList, Object.assign({}, rest, { cols: cols, rowActions: newRowActions, toolbarActions: newToolbarActions, depends: depends, handle: innerHandle })) }));
342
360
  };
343
361
  export default DataList;
344
362
  const RefreshButton = ({ show, onClick, size = 18 }) => {
@@ -1,6 +1,7 @@
1
1
  import { Columns, Eye, RefreshCcw, Search, Trash } from "lucide-react";
2
- import React, { useCallback, useState } from "react";
2
+ import React, { useCallback, useRef, useState } from "react";
3
3
  import { useApp } from "../../core/AppContext";
4
+ import { useDataContext } from "../../core/DataContext";
4
5
  import { DynamicComponent } from "../../core/DynamicComponent";
5
6
  import { usePageContext } from "../../core/PageContext";
6
7
  import Panel from "../../core/Panel";
@@ -123,6 +124,7 @@ export interface DataListAttr {
123
124
  rowClassName?: (row: any, rowIndex: number) => string;
124
125
 
125
126
  depends?: string;
127
+ handle?: InnerDataListHandle;
126
128
  }
127
129
 
128
130
  // ============================================================================
@@ -191,6 +193,8 @@ const InnerDataList: React.FC<InnerProps> = ({
191
193
  className = "",
192
194
  rowClassName,
193
195
  depends,
196
+
197
+ handle,
194
198
  }) => {
195
199
  const { listHandler, columns, rows, setRows, loading: ctxLoading, setLoading } = useTableContext();
196
200
 
@@ -211,6 +215,19 @@ const InnerDataList: React.FC<InnerProps> = ({
211
215
 
212
216
  const pageContext = usePageContext();
213
217
 
218
+ const handleRef = {
219
+ setFilter: async (filter: Record<string, any>): Promise<void> => {
220
+ if (filter == null || listHandler == null) return;
221
+
222
+ await listHandler.setFilter(filter);
223
+ await loadData();
224
+ },
225
+ };
226
+
227
+ if (handle != null) {
228
+ handle.init(handleRef);
229
+ }
230
+
214
231
  const refreshHandler = async () => {
215
232
  // console.log("refresh via notify depends", pageContext?.uuid);
216
233
  await handleRefresh();
@@ -611,12 +628,17 @@ const InnerDataList: React.FC<InnerProps> = ({
611
628
  // - resolves dynamic filter params before passing to TableProvider
612
629
  // ============================================================================
613
630
 
631
+ type InnerDataListHandle = {
632
+ init: (ref: any) => void;
633
+ };
634
+
614
635
  export const DataList: React.FC<DataListProps> = ({ attr }) => {
615
- const { cols, data, rowsPerPage, disableTotalCount, commonActions, rowActions, toolbarActions, ...rest } = attr;
636
+ const { depends, cols, data, rowsPerPage, disableTotalCount, commonActions, rowActions, toolbarActions, ...rest } = attr;
616
637
 
617
638
  // All hooks called here — this is a React component so hooks are valid
618
639
  const { tenant, module } = useApp();
619
640
  const pageContext = usePageContext();
641
+ const dataContext = useDataContext();
620
642
 
621
643
  // Factory defined here — ListHandler is a local import, no prop needed
622
644
  const listHandlerFactory = (config: ListHandlerConfig): ListActionHandler => ListHandler(config);
@@ -676,11 +698,27 @@ export const DataList: React.FC<DataListProps> = ({ attr }) => {
676
698
  component: "LookupPage",
677
699
  attr: {
678
700
  ...commonActions.filterPage,
701
+ name: "customFilter",
679
702
  title: "Filter",
680
703
  },
681
704
  });
682
705
  }
683
706
 
707
+ const handleRef = useRef<any>(null);
708
+
709
+ const onRefresh = () => {
710
+ const filter = dataContext?.get("customFilter");
711
+ handleRef.current?.setFilter(filter ?? {});
712
+ };
713
+
714
+ useDependHandler({ name: "customFilter", onRefresh });
715
+
716
+ const innerHandle = {
717
+ init: (ref: any) => {
718
+ handleRef.current = ref;
719
+ },
720
+ } as InnerDataListHandle;
721
+
684
722
  return (
685
723
  <TableProvider
686
724
  data={data}
@@ -692,7 +730,7 @@ export const DataList: React.FC<DataListProps> = ({ attr }) => {
692
730
  module={module ?? ""}
693
731
  resolvedParams={resolveParams()}
694
732
  >
695
- <InnerDataList {...rest} cols={cols} rowActions={newRowActions} toolbarActions={newToolbarActions} />
733
+ <InnerDataList {...rest} cols={cols} rowActions={newRowActions} toolbarActions={newToolbarActions} depends={depends} handle={innerHandle} />
696
734
  </TableProvider>
697
735
  );
698
736
  };
@@ -35,7 +35,7 @@ export const ListHandler = (config) => {
35
35
  let hasMorePages = false;
36
36
  const getData = () => dataResult;
37
37
  const load = async () => {
38
- var _a, _b;
38
+ var _a, _b, _c, _d;
39
39
  const queryParams = {
40
40
  cols,
41
41
  start: (currentPage - 1) * rowsPerPage,
@@ -58,10 +58,10 @@ export const ListHandler = (config) => {
58
58
  // console.log("datalist pass 1", { api, query: { ...queryParams, ...rest } });
59
59
  const result = await localAPI.exec(`/services/exec/${config.tenant}/${config.module}/${api}`, Object.assign(Object.assign({}, queryParams), rest));
60
60
  // console.log("datalist pass 2", { result });
61
- dataResult = result.data || [];
61
+ dataResult = (_b = (_a = result.data) !== null && _a !== void 0 ? _a : result) !== null && _b !== void 0 ? _b : [];
62
62
  if (useCursorPagination) {
63
- lastCursor = (_a = result.nextCursor) !== null && _a !== void 0 ? _a : null;
64
- hasMorePages = (_b = result.hasMore) !== null && _b !== void 0 ? _b : false;
63
+ lastCursor = (_c = result.nextCursor) !== null && _c !== void 0 ? _c : null;
64
+ hasMorePages = (_d = result.hasMore) !== null && _d !== void 0 ? _d : false;
65
65
  }
66
66
  if (!disableTotalCount) {
67
67
  totalRecordCount = result.total || 0;
@@ -122,7 +122,7 @@ export const ListHandler = (config: ListHandlerConfig): ListActionHandler => {
122
122
  // console.log("datalist pass 1", { api, query: { ...queryParams, ...rest } });
123
123
  const result = await localAPI.exec(`/services/exec/${config.tenant}/${config.module}/${api}`, { ...queryParams, ...rest });
124
124
  // console.log("datalist pass 2", { result });
125
- dataResult = result.data || [];
125
+ dataResult = result.data ?? result ?? [];
126
126
 
127
127
  if (useCursorPagination) {
128
128
  lastCursor = result.nextCursor ?? null;
@@ -1,11 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { DynamicComponent } from "../../core/DynamicComponent";
2
+ import { localAPI } from "@ramesesinc/lib/local-api";
3
+ import React, { useEffect, useMemo, useState } from "react";
3
4
  import { useApp } from "../../core/AppContext";
5
+ import { DynamicComponent } from "../../core/DynamicComponent";
4
6
  import useDependHandler from "../../core/UIDependHandler";
5
- import { localAPI } from "@ramesesinc/lib/local-api";
6
- import { useEffect, useMemo, useState } from "react";
7
7
  import UIComponent from "../common/UIComponent";
8
- import React from "react";
9
8
  // ─────────────────────────────────────────────────────────────────────────────
10
9
  // Attr Parser
11
10
  // Handles both strict JSON {"key":"value"} and JS object literals {key: "value"}
@@ -19,7 +18,9 @@ function parseAttr(attrStr) {
19
18
  catch (_a) {
20
19
  try {
21
20
  // eslint-disable-next-line no-new-func
22
- return new Function(`"use strict"; return (${attrStr})`)();
21
+ // return new Function(`"use strict"; return (${attrStr})`)();
22
+ const normalized = attrStr.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)(\s*:)/g, '$1"$2"$3').replace(/'([^'\\]*(\\.[^'\\]*)*)'/g, '"$1"');
23
+ return JSON.parse(normalized);
23
24
  }
24
25
  catch (_b) {
25
26
  console.warn("HtmlForm: could not parse attr →", attrStr);
@@ -59,9 +60,7 @@ function domAttrsToProps(el) {
59
60
  if (["component", "attr"].includes(attr.name))
60
61
  continue;
61
62
  const reactName = (_a = ATTR_MAP[attr.name]) !== null && _a !== void 0 ? _a : attr.name;
62
- props[reactName] = attr.name === "style"
63
- ? styleStringToObject(attr.value)
64
- : attr.value;
63
+ props[reactName] = attr.name === "style" ? styleStringToObject(attr.value) : attr.value;
65
64
  }
66
65
  return props;
67
66
  }
@@ -95,16 +94,14 @@ function walkNode(node, parentTag) {
95
94
  if (tag === "span" && componentName) {
96
95
  const attr = parseAttr(el.getAttribute("attr"));
97
96
  const config = { component: componentName, attr };
98
- return (_jsx(DynamicComponent, { config: config }, nextKey()));
97
+ return _jsx(DynamicComponent, { config: config }, nextKey());
99
98
  }
100
99
  // ── Regular element → recurse ──────────────────────────────────────────
101
100
  const props = Object.assign(Object.assign({}, domAttrsToProps(el)), { key: nextKey() });
102
101
  const children = Array.from(el.childNodes)
103
102
  .map((child) => walkNode(child, tag)) // pass current tag as parentTag
104
103
  .filter((n) => n !== null && n !== undefined);
105
- return children.length > 0
106
- ? React.createElement(tag, props, ...children)
107
- : React.createElement(tag, props);
104
+ return children.length > 0 ? React.createElement(tag, props, ...children) : React.createElement(tag, props);
108
105
  }
109
106
  return null;
110
107
  }
@@ -1,10 +1,9 @@
1
- import { DynamicComponent } from "../../core/DynamicComponent";
1
+ import { localAPI } from "@ramesesinc/lib/local-api";
2
+ import React, { useEffect, useMemo, useState } from "react";
2
3
  import { useApp } from "../../core/AppContext";
4
+ import { DynamicComponent } from "../../core/DynamicComponent";
3
5
  import useDependHandler from "../../core/UIDependHandler";
4
- import { localAPI } from "@ramesesinc/lib/local-api";
5
- import { useEffect, useMemo, useState } from "react";
6
6
  import UIComponent from "../common/UIComponent";
7
- import React from "react";
8
7
 
9
8
  // ─────────────────────────────────────────────────────────────────────────────
10
9
  // Types
@@ -33,7 +32,9 @@ function parseAttr(attrStr: string | null): any {
33
32
  } catch {
34
33
  try {
35
34
  // eslint-disable-next-line no-new-func
36
- return new Function(`"use strict"; return (${attrStr})`)();
35
+ // return new Function(`"use strict"; return (${attrStr})`)();
36
+ const normalized = attrStr.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)(\s*:)/g, '$1"$2"$3').replace(/'([^'\\]*(\\.[^'\\]*)*)'/g, '"$1"');
37
+ return JSON.parse(normalized);
37
38
  } catch {
38
39
  console.warn("HtmlForm: could not parse attr →", attrStr);
39
40
  return {};
@@ -74,9 +75,7 @@ function domAttrsToProps(el: Element): Record<string, any> {
74
75
  for (const attr of Array.from(el.attributes)) {
75
76
  if (["component", "attr"].includes(attr.name)) continue;
76
77
  const reactName = ATTR_MAP[attr.name] ?? attr.name;
77
- props[reactName] = attr.name === "style"
78
- ? styleStringToObject(attr.value)
79
- : attr.value;
78
+ props[reactName] = attr.name === "style" ? styleStringToObject(attr.value) : attr.value;
80
79
  }
81
80
  return props;
82
81
  }
@@ -116,23 +115,16 @@ function walkNode(node: ChildNode, parentTag?: string): React.ReactNode {
116
115
  if (tag === "span" && componentName) {
117
116
  const attr: any = parseAttr(el.getAttribute("attr"));
118
117
  const config: SentinelConfig = { component: componentName, attr };
119
- return (
120
- <DynamicComponent
121
- key={nextKey()}
122
- config={config}
123
- />
124
- );
118
+ return <DynamicComponent key={nextKey()} config={config} />;
125
119
  }
126
120
 
127
121
  // ── Regular element → recurse ──────────────────────────────────────────
128
122
  const props = { ...domAttrsToProps(el), key: nextKey() };
129
123
  const children: React.ReactNode[] = Array.from(el.childNodes)
130
- .map((child) => walkNode(child, tag)) // pass current tag as parentTag
124
+ .map((child) => walkNode(child, tag)) // pass current tag as parentTag
131
125
  .filter((n) => n !== null && n !== undefined);
132
126
 
133
- return children.length > 0
134
- ? React.createElement(tag, props, ...children)
135
- : React.createElement(tag, props);
127
+ return children.length > 0 ? React.createElement(tag, props, ...children) : React.createElement(tag, props);
136
128
  }
137
129
 
138
130
  return null;
@@ -176,11 +168,9 @@ const HtmlForm = (props: HtmlFormProps) => {
176
168
 
177
169
  return (
178
170
  <UIComponent {...(props ?? {})}>
179
- <div className="h-[calc(100vh-80px)] overflow-y-auto overflow-x-auto">
180
- {reactTree}
181
- </div>
171
+ <div className="h-[calc(100vh-80px)] overflow-y-auto overflow-x-auto">{reactTree}</div>
182
172
  </UIComponent>
183
173
  );
184
174
  };
185
175
 
186
- export default HtmlForm;
176
+ export default HtmlForm;
@@ -8,37 +8,26 @@ import { render } from "../../lib/utils/ExprUtil";
8
8
  import UIComponent from "../common/UIComponent";
9
9
  const Html = (props) => {
10
10
  const { depends, expr, templateid } = props !== null && props !== void 0 ? props : {};
11
- const [htmlTemplate, setHtmlTemplate] = useState(""); // Store the raw template
12
- const [renderedHtml, setRenderedHtml] = useState(""); // Store the rendered HTML
13
- const [resData, setResData] = useState({});
11
+ const [htmlTemplate, setHtmlTemplate] = useState(""); // raw template
12
+ const [renderedHtml, setRenderedHtml] = useState(""); // rendered output
14
13
  const pageContext = usePageContext();
15
14
  const binding = pageContext === null || pageContext === void 0 ? void 0 : pageContext.binding;
16
15
  const { tenant, module } = useApp();
17
- const loadHtmlContent = async (tempid) => {
16
+ // single render function used everywhere
17
+ const renderTemplate = (template) => {
18
18
  var _a;
19
+ if (!template)
20
+ return "";
19
21
  try {
20
- const htmlContent = await localAPI.useMgmt(tenant, module).get("html_templates", tempid);
21
- if (htmlContent != null) {
22
- // Store the raw template
23
- setHtmlTemplate(htmlContent.htmlCode);
24
- // Fetch data from API
25
- const data = (_a = pageContext === null || pageContext === void 0 ? void 0 : pageContext.getAllData()) !== null && _a !== void 0 ? _a : {};
26
- // Render the template with the data
27
- const rendered = render(htmlContent.htmlCode, data);
28
- setRenderedHtml(rendered);
29
- }
30
- else {
31
- setHtmlTemplate("");
32
- setRenderedHtml("");
33
- }
22
+ const data = (_a = pageContext === null || pageContext === void 0 ? void 0 : pageContext.getAllData()) !== null && _a !== void 0 ? _a : {};
23
+ return render(template, data);
34
24
  }
35
25
  catch (error) {
36
- console.error("Error loading HTML template:", error);
37
- setHtmlTemplate("");
38
- setRenderedHtml("");
26
+ console.error("Template render error:", error);
27
+ return template;
39
28
  }
40
29
  };
41
- // Render the expression using ExprUtil
30
+ // single expression render
42
31
  const renderExpression = (expression) => {
43
32
  var _a;
44
33
  if (!expression)
@@ -52,34 +41,46 @@ const Html = (props) => {
52
41
  return expression;
53
42
  }
54
43
  };
55
- const initialValue = renderExpression(expr !== null && expr !== void 0 ? expr : "");
56
- const [value, setValue] = useState(initialValue);
57
- const onRefresh = () => {
58
- const newValue = renderExpression(expr !== null && expr !== void 0 ? expr : "");
59
- setValue(newValue);
60
- // Re-render the HTML template if data changes
61
- if (htmlTemplate && resData) {
62
- const rendered = render(htmlTemplate, resData);
63
- setRenderedHtml(rendered);
64
- }
65
- };
66
- useDependHandler({ name: depends, onRefresh });
44
+ // load template only once when templateid changes
67
45
  useEffect(() => {
68
- if (templateid != null && templateid.trim() !== "") {
69
- loadHtmlContent(templateid);
70
- }
46
+ if (templateid == null || templateid.trim() === "")
47
+ return;
48
+ const loadHtmlContent = async () => {
49
+ try {
50
+ const htmlContent = await localAPI.useMgmt(tenant, module).get("html_templates", templateid);
51
+ if (htmlContent != null) {
52
+ setHtmlTemplate(htmlContent.htmlCode); // ← just store raw template
53
+ }
54
+ else {
55
+ setHtmlTemplate("");
56
+ }
57
+ }
58
+ catch (error) {
59
+ console.error("Error loading HTML template:", error);
60
+ setHtmlTemplate("");
61
+ }
62
+ };
63
+ loadHtmlContent();
71
64
  }, [templateid]);
65
+ // ← re-render whenever template OR binding data changes
72
66
  useEffect(() => {
73
- const newValue = renderExpression(expr !== null && expr !== void 0 ? expr : "");
74
- setValue(newValue);
75
- }, [binding === null || binding === void 0 ? void 0 : binding.raw, expr]);
76
- // Re-render template when data changes
77
- useEffect(() => {
78
- if (htmlTemplate && resData && Object.keys(resData).length > 0) {
79
- const rendered = render(htmlTemplate, resData);
80
- setRenderedHtml(rendered);
67
+ if (htmlTemplate) {
68
+ setRenderedHtml(renderTemplate(htmlTemplate));
81
69
  }
82
- }, [resData, htmlTemplate]);
70
+ else if (expr) {
71
+ setRenderedHtml(renderExpression(expr));
72
+ }
73
+ }, [htmlTemplate, binding === null || binding === void 0 ? void 0 : binding.raw]); // ← binding.raw triggers when data changes
74
+ // ← onRefresh called when depends changes
75
+ const onRefresh = () => {
76
+ if (htmlTemplate) {
77
+ setRenderedHtml(renderTemplate(htmlTemplate));
78
+ }
79
+ else if (expr) {
80
+ setRenderedHtml(renderExpression(expr));
81
+ }
82
+ };
83
+ useDependHandler({ name: depends, onRefresh });
83
84
  return (_jsx(UIComponent, Object.assign({}, (props !== null && props !== void 0 ? props : {}), { children: _jsx("div", { className: "h-[calc(100vh-80px)] overflow-y-auto overflow-x-auto", dangerouslySetInnerHTML: { __html: renderedHtml } }) })));
84
85
  };
85
86
  export default Html;
@@ -16,39 +16,26 @@ type HtmlLabelProps = {
16
16
 
17
17
  const Html = (props: HtmlLabelProps) => {
18
18
  const { depends, expr, templateid } = props ?? {};
19
- const [htmlTemplate, setHtmlTemplate] = useState(""); // Store the raw template
20
- const [renderedHtml, setRenderedHtml] = useState(""); // Store the rendered HTML
21
- const [resData, setResData] = useState<Record<string, any>>({});
19
+
20
+ const [htmlTemplate, setHtmlTemplate] = useState(""); // raw template
21
+ const [renderedHtml, setRenderedHtml] = useState(""); // rendered output
22
22
  const pageContext = usePageContext();
23
23
  const binding = pageContext?.binding;
24
24
  const { tenant, module } = useApp();
25
25
 
26
- const loadHtmlContent = async (tempid: string) => {
26
+ // single render function used everywhere
27
+ const renderTemplate = (template: string) => {
28
+ if (!template) return "";
27
29
  try {
28
- const htmlContent = await localAPI.useMgmt(tenant!, module!).get("html_templates", tempid);
29
-
30
- if (htmlContent != null) {
31
- // Store the raw template
32
- setHtmlTemplate(htmlContent.htmlCode);
33
-
34
- // Fetch data from API
35
- const data = pageContext?.getAllData() ?? {};
36
-
37
- // Render the template with the data
38
- const rendered = render(htmlContent.htmlCode, data);
39
- setRenderedHtml(rendered);
40
- } else {
41
- setHtmlTemplate("");
42
- setRenderedHtml("");
43
- }
30
+ const data = pageContext?.getAllData() ?? {};
31
+ return render(template, data);
44
32
  } catch (error) {
45
- console.error("Error loading HTML template:", error);
46
- setHtmlTemplate("");
47
- setRenderedHtml("");
33
+ console.error("Template render error:", error);
34
+ return template;
48
35
  }
49
36
  };
50
37
 
51
- // Render the expression using ExprUtil
38
+ // single expression render
52
39
  const renderExpression = (expression: string) => {
53
40
  if (!expression) return "";
54
41
  try {
@@ -60,52 +47,49 @@ const Html = (props: HtmlLabelProps) => {
60
47
  }
61
48
  };
62
49
 
63
- const initialValue = renderExpression(expr ?? "");
64
- const [value, setValue] = useState(initialValue);
65
-
66
- const onRefresh = () => {
67
- const newValue = renderExpression(expr ?? "");
68
- setValue(newValue);
69
-
70
- // Re-render the HTML template if data changes
71
- if (htmlTemplate && resData) {
72
- const rendered = render(htmlTemplate, resData);
73
- setRenderedHtml(rendered);
74
- }
75
- };
76
-
77
- useDependHandler({ name: depends, onRefresh });
78
-
50
+ // load template only once when templateid changes
79
51
  useEffect(() => {
80
- if (templateid != null && templateid.trim() !== "") {
81
- loadHtmlContent(templateid);
82
- }
52
+ if (templateid == null || templateid.trim() === "") return;
53
+
54
+ const loadHtmlContent = async () => {
55
+ try {
56
+ const htmlContent = await localAPI.useMgmt(tenant!, module!).get("html_templates", templateid);
57
+ if (htmlContent != null) {
58
+ setHtmlTemplate(htmlContent.htmlCode); // ← just store raw template
59
+ } else {
60
+ setHtmlTemplate("");
61
+ }
62
+ } catch (error) {
63
+ console.error("Error loading HTML template:", error);
64
+ setHtmlTemplate("");
65
+ }
66
+ };
67
+
68
+ loadHtmlContent();
83
69
  }, [templateid]);
84
70
 
71
+ // ← re-render whenever template OR binding data changes
85
72
  useEffect(() => {
86
- const newValue = renderExpression(expr ?? "");
87
- setValue(newValue);
88
- }, [binding?.raw, expr]);
73
+ if (htmlTemplate) {
74
+ setRenderedHtml(renderTemplate(htmlTemplate));
75
+ } else if (expr) {
76
+ setRenderedHtml(renderExpression(expr));
77
+ }
78
+ }, [htmlTemplate, binding?.raw]); // ← binding.raw triggers when data changes
89
79
 
90
- // Re-render template when data changes
91
- useEffect(() => {
92
- if (htmlTemplate && resData && Object.keys(resData).length > 0) {
93
- const rendered = render(htmlTemplate, resData);
94
- setRenderedHtml(rendered);
80
+ // onRefresh called when depends changes
81
+ const onRefresh = () => {
82
+ if (htmlTemplate) {
83
+ setRenderedHtml(renderTemplate(htmlTemplate));
84
+ } else if (expr) {
85
+ setRenderedHtml(renderExpression(expr));
95
86
  }
96
- }, [resData, htmlTemplate]);
87
+ };
88
+
89
+ useDependHandler({ name: depends, onRefresh });
97
90
 
98
91
  return (
99
92
  <UIComponent {...(props ?? {})}>
100
- {/* Debug info - remove in production */}
101
- {/* <details style={{ marginBottom: "20px" }}>
102
- <summary>Debug Data</summary>
103
- <pre style={{ fontSize: "10px", maxHeight: "200px", overflow: "auto" }}>
104
- {JSON.stringify(resData, null, 2)}
105
- </pre>
106
- </details> */}
107
-
108
- {/* Rendered HTML */}
109
93
  <div className="h-[calc(100vh-80px)] overflow-y-auto overflow-x-auto" dangerouslySetInnerHTML={{ __html: renderedHtml }} />
110
94
  </UIComponent>
111
95
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramesesinc/platform-core",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Platform Core Library",
5
5
  "author": "Rameses Systems Inc.",
6
6
  "license": "MIT",
@@ -28,12 +28,12 @@
28
28
  "copy-res": "node -e \"const fs=require('fs');const apps=['etracs','agos','filipizen'];apps.forEach(a=>{const d='../../apps/'+a+'/public/_res';fs.cpSync('src/public/_res',d,{recursive:true,force:true})})\""
29
29
  },
30
30
  "peerDependencies": {
31
+ "clsx": "^2.1.1",
32
+ "lucide-react": "^0.553.0",
31
33
  "next": ">=13.5.6 <15.0.0",
32
34
  "nunjucks": "^3.2.4",
33
35
  "react": ">=18.2.0",
34
- "react-dom": ">=18.2.0",
35
- "lucide-react": "^0.553.0",
36
- "clsx": "^2.1.1"
36
+ "react-dom": ">=18.2.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@ramesesinc/app-forms": "0.1.0",
@@ -50,13 +50,14 @@
50
50
  "clsx": "^2.1.1",
51
51
  "eslint": "^8",
52
52
  "eslint-config-next": "14.2.9",
53
+ "next": ">=13.5.6 <15.0.0",
53
54
  "postcss": "^8",
54
55
  "postcss-cli": "^11.0.1",
55
56
  "supports-color": "^10.2.2",
56
57
  "tailwind-merge": "^3.3.1",
57
58
  "tailwindcss": "^3.4.1",
58
- "typescript": "^5",
59
- "next": ">=13.5.6 <15.0.0"
59
+ "expression-eval": "^5.0.1",
60
+ "typescript": "^5"
60
61
  },
61
62
  "keywords": [
62
63
  "ramesesinc-platform-core"