@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.
- package/dist/components/common/UIComponent.js +3 -1
- package/dist/components/common/UIComponent.tsx +3 -1
- package/dist/components/output/Label.d.ts +0 -1
- package/dist/components/output/Label.tsx +0 -1
- package/dist/components/table/DataList.d.ts +4 -0
- package/dist/components/table/DataList.js +30 -12
- package/dist/components/table/DataList.tsx +41 -3
- package/dist/components/table/ListHandler.js +4 -4
- package/dist/components/table/ListHandler.ts +1 -1
- package/dist/components/view/HtmlForm.js +9 -12
- package/dist/components/view/HtmlForm.tsx +12 -22
- package/dist/components/view/HtmlView.js +47 -46
- package/dist/components/view/HtmlView.tsx +45 -61
- package/package.json +7 -6
|
@@ -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;
|
|
@@ -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
|
-
|
|
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 = (
|
|
64
|
-
hasMorePages = (
|
|
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 {
|
|
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
|
|
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 {
|
|
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))
|
|
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(""); //
|
|
12
|
-
const [renderedHtml, setRenderedHtml] = useState(""); //
|
|
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
|
-
|
|
16
|
+
// ← single render function used everywhere
|
|
17
|
+
const renderTemplate = (template) => {
|
|
18
18
|
var _a;
|
|
19
|
+
if (!template)
|
|
20
|
+
return "";
|
|
19
21
|
try {
|
|
20
|
-
const
|
|
21
|
-
|
|
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("
|
|
37
|
-
|
|
38
|
-
setRenderedHtml("");
|
|
26
|
+
console.error("Template render error:", error);
|
|
27
|
+
return template;
|
|
39
28
|
}
|
|
40
29
|
};
|
|
41
|
-
//
|
|
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
|
-
|
|
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
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
const [
|
|
21
|
-
const [
|
|
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
|
-
|
|
26
|
+
// ← single render function used everywhere
|
|
27
|
+
const renderTemplate = (template: string) => {
|
|
28
|
+
if (!template) return "";
|
|
27
29
|
try {
|
|
28
|
-
const
|
|
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("
|
|
46
|
-
|
|
47
|
-
setRenderedHtml("");
|
|
33
|
+
console.error("Template render error:", error);
|
|
34
|
+
return template;
|
|
48
35
|
}
|
|
49
36
|
};
|
|
50
37
|
|
|
51
|
-
//
|
|
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
|
-
|
|
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
|
|
81
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
//
|
|
91
|
-
|
|
92
|
-
if (htmlTemplate
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
}
|
|
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
|
+
"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
|
-
"
|
|
59
|
-
"
|
|
59
|
+
"expression-eval": "^5.0.1",
|
|
60
|
+
"typescript": "^5"
|
|
60
61
|
},
|
|
61
62
|
"keywords": [
|
|
62
63
|
"ramesesinc-platform-core"
|