@ramesesinc/platform-core 0.1.5 → 0.1.8
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/action/LookupPage.js +9 -31
- package/dist/components/action/ViewPage.d.ts +2 -0
- package/dist/components/action/ViewPage.js +25 -31
- package/dist/components/common/UIComponent.js +4 -3
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/table/DataList.js +2 -2
- package/dist/components/view/PopupView.d.ts +13 -0
- package/dist/components/view/PopupView.js +25 -20
- package/dist/core/DataContext.d.ts +7 -4
- package/dist/core/DataContext.js +16 -4
- package/dist/core/Page.js +25 -26
- package/dist/core/PageCache.js +16 -3
- package/dist/core/PageContext.js +90 -18
- package/dist/core/PageViewContext.d.ts +13 -1
- package/dist/core/PageViewContext.js +89 -5
- package/dist/core/PopupContext.d.ts +49 -0
- package/dist/core/PopupContext.js +380 -0
- package/dist/core/RowContext.js +1 -1
- package/dist/core/WindowContext.d.ts +15 -0
- package/dist/core/WindowContext.js +28 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/index.css +25 -7
- package/dist/lib/utils/BeanUtils.js +7 -7
- package/dist/templates/DataListTemplate.js +7 -2
- package/dist/templates/ExplorerTemplate.js +1 -1
- package/package.json +5 -5
- package/dist/components/action/AlertMessage.tsx +0 -38
- package/dist/components/action/Button.tsx +0 -230
- package/dist/components/action/CancelEdit.tsx +0 -40
- package/dist/components/action/DeleteData.tsx +0 -73
- package/dist/components/action/Edit.tsx +0 -40
- package/dist/components/action/LookupPage.tsx +0 -113
- package/dist/components/action/ProcessRunner.tsx +0 -337
- package/dist/components/action/Refresh.tsx +0 -35
- package/dist/components/action/SaveData.tsx +0 -74
- package/dist/components/action/SelectData.tsx +0 -47
- package/dist/components/action/Undo.tsx +0 -50
- package/dist/components/action/UpdateContext.tsx +0 -40
- package/dist/components/action/UpdateData.tsx +0 -49
- package/dist/components/action/ViewBackPage.tsx +0 -46
- package/dist/components/action/ViewPage.tsx +0 -141
- package/dist/components/common/UIComponent.tsx +0 -86
- package/dist/components/common/UIInput.tsx +0 -49
- package/dist/components/common/UIMenu.tsx +0 -91
- package/dist/components/index.ts +0 -51
- package/dist/components/input/CodeEditor.tsx +0 -188
- package/dist/components/input/DateField.tsx +0 -274
- package/dist/components/input/DayPicker.tsx +0 -5
- package/dist/components/input/HtmlCode.tsx +0 -203
- package/dist/components/input/JsonCode.tsx +0 -205
- package/dist/components/input/MonthPicker.tsx +0 -5
- package/dist/components/input/ScriptCode.tsx +0 -195
- package/dist/components/input/Select.tsx +0 -78
- package/dist/components/input/SqlCode.tsx +0 -162
- package/dist/components/input/StringDecision.tsx +0 -64
- package/dist/components/input/Text.tsx +0 -57
- package/dist/components/input/YearPicker.tsx +0 -81
- package/dist/components/list/IconMenu.tsx +0 -115
- package/dist/components/list/TabMenu.tsx +0 -127
- package/dist/components/list/TreeMenu.tsx +0 -279
- package/dist/components/list/TxnTaskList.tsx +0 -198
- package/dist/components/output/Label.tsx +0 -50
- package/dist/components/table/DataList.tsx +0 -820
- package/dist/components/table/DataTable.tsx +0 -572
- package/dist/components/table/ListHandler.ts +0 -276
- package/dist/components/table/TableContext.tsx +0 -122
- package/dist/components/view/ComponentView.tsx +0 -102
- package/dist/components/view/FilterView.tsx +0 -21
- package/dist/components/view/HtmlForm.tsx +0 -176
- package/dist/components/view/HtmlView.tsx +0 -98
- package/dist/components/view/IFrameView.tsx +0 -48
- package/dist/components/view/Modal.tsx +0 -72
- package/dist/components/view/PageView.tsx +0 -131
- package/dist/components/view/PopupView.tsx +0 -160
- package/dist/components/view/RootView.tsx +0 -109
- package/dist/components/view/WizardView.tsx +0 -48
- package/dist/lib/layouts/BorderLayout.tsx +0 -31
- package/dist/lib/layouts/CardLayout.tsx +0 -73
- package/dist/lib/layouts/CenterLayout.tsx +0 -20
- package/dist/lib/layouts/GridLayout.tsx +0 -20
- package/dist/lib/layouts/HPanel.tsx +0 -31
- package/dist/lib/layouts/HorizontalLayout.tsx +0 -29
- package/dist/lib/layouts/MainLayout.tsx +0 -16
- package/dist/lib/layouts/PageLayout.tsx +0 -29
- package/dist/lib/layouts/VPanel.tsx +0 -27
- package/dist/lib/layouts/XLayout.tsx +0 -29
- package/dist/lib/layouts/YLayout.tsx +0 -29
- package/dist/lib/layouts/index.ts +0 -13
- /package/dist/components/action/{UpdateContext.d.ts → UpdateState.d.ts} +0 -0
- /package/dist/components/action/{UpdateContext.js → UpdateState.js} +0 -0
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ListHandler - Functional style implementation for managing data fetching,
|
|
3
|
-
* pagination, filtering, and sorting for DataList, DataTable, or any list-like components.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { localAPI } from "@ramesesinc/lib/local-api";
|
|
7
|
-
|
|
8
|
-
export interface ListHandlerConfig {
|
|
9
|
-
tenant: string;
|
|
10
|
-
module: string;
|
|
11
|
-
api: string;
|
|
12
|
-
params?: Record<string, any>;
|
|
13
|
-
cols: string[];
|
|
14
|
-
rowsPerPage?: number;
|
|
15
|
-
disableTotalCount?: boolean;
|
|
16
|
-
useCursorPagination?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface QueryParams {
|
|
20
|
-
cols: string[];
|
|
21
|
-
start: number;
|
|
22
|
-
limit: number;
|
|
23
|
-
sort?: {
|
|
24
|
-
column: string;
|
|
25
|
-
direction: "asc" | "desc";
|
|
26
|
-
};
|
|
27
|
-
search?: string;
|
|
28
|
-
skipCount?: boolean;
|
|
29
|
-
cursor?: string;
|
|
30
|
-
[key: string]: any;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface DataResult {
|
|
34
|
-
data: any[];
|
|
35
|
-
total?: number;
|
|
36
|
-
page: number;
|
|
37
|
-
totalPages?: number;
|
|
38
|
-
hasMore?: boolean;
|
|
39
|
-
nextCursor?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type ListActionHandler = {
|
|
43
|
-
getData: () => any[];
|
|
44
|
-
load: () => Promise<void>;
|
|
45
|
-
reset: () => Promise<void>;
|
|
46
|
-
moveNextPage: () => Promise<void>;
|
|
47
|
-
movePrevPage: () => Promise<void>;
|
|
48
|
-
moveFirstPage: () => Promise<void>;
|
|
49
|
-
moveLastPage: () => Promise<void>;
|
|
50
|
-
moveToPage: (page: number) => Promise<void>;
|
|
51
|
-
setFilter: (filter: Record<string, any>) => Promise<void>;
|
|
52
|
-
resetFilter: () => Promise<void>;
|
|
53
|
-
doSearch: (text: string) => Promise<void>;
|
|
54
|
-
sortAsc: (column: string) => Promise<void>;
|
|
55
|
-
sortDesc: (column: string) => Promise<void>;
|
|
56
|
-
setRowsPerPage: (rows: number) => Promise<void>;
|
|
57
|
-
hasNextPage: () => boolean;
|
|
58
|
-
hasPrevPage: () => boolean;
|
|
59
|
-
getTotalPageCount: () => number;
|
|
60
|
-
getTotalRecordCount: () => number;
|
|
61
|
-
getCurrentPage: () => number;
|
|
62
|
-
getCurrentSortedColumn: () => string | null;
|
|
63
|
-
getCurrentSortDirection: () => "asc" | "desc" | null;
|
|
64
|
-
getRowsPerPage: () => number;
|
|
65
|
-
getSearchText: () => string;
|
|
66
|
-
getCustomFilter: () => Record<string, any>;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// FIX: removed the erroneous second parameter `ListHandlerConfig: any`
|
|
70
|
-
export const ListHandler = (config: ListHandlerConfig): ListActionHandler => {
|
|
71
|
-
const api = config.api;
|
|
72
|
-
const cols = config.cols;
|
|
73
|
-
|
|
74
|
-
const { filter = {}, ...rest } = config.params ?? {};
|
|
75
|
-
|
|
76
|
-
const disableTotalCount = config.disableTotalCount || false;
|
|
77
|
-
const useCursorPagination = config.useCursorPagination || false;
|
|
78
|
-
|
|
79
|
-
let rowsPerPage = config.rowsPerPage || 20;
|
|
80
|
-
let currentPage = 1;
|
|
81
|
-
let customFilter: Record<string, any> = {};
|
|
82
|
-
let searchText = "";
|
|
83
|
-
let sortColumn: string | null = null;
|
|
84
|
-
let sortDirection: "asc" | "desc" | null = null;
|
|
85
|
-
let lastCursor: string | null = null;
|
|
86
|
-
|
|
87
|
-
let dataResult: any[] = [];
|
|
88
|
-
let totalRecordCount = 0;
|
|
89
|
-
let totalPageCount = 0;
|
|
90
|
-
let hasMorePages = false;
|
|
91
|
-
|
|
92
|
-
const getData = (): any[] => dataResult;
|
|
93
|
-
|
|
94
|
-
const load = async (): Promise<void> => {
|
|
95
|
-
const queryParams: QueryParams = {
|
|
96
|
-
cols,
|
|
97
|
-
start: (currentPage - 1) * rowsPerPage,
|
|
98
|
-
limit: rowsPerPage,
|
|
99
|
-
filter: {
|
|
100
|
-
...filter,
|
|
101
|
-
...customFilter,
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
if (useCursorPagination && lastCursor) {
|
|
106
|
-
queryParams.cursor = lastCursor;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (disableTotalCount) {
|
|
110
|
-
queryParams.skipCount = true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (searchText) {
|
|
114
|
-
queryParams.search = searchText;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (sortColumn && sortDirection) {
|
|
118
|
-
queryParams.sort = { column: sortColumn, direction: sortDirection };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
// console.log("datalist pass 1", { api, query: { ...queryParams, ...rest } });
|
|
123
|
-
const result = await localAPI.exec(`/services/exec/${config.tenant}/${config.module}/${api}`, { ...queryParams, ...rest });
|
|
124
|
-
// console.log("datalist pass 2", { result });
|
|
125
|
-
dataResult = result.data ?? result ?? [];
|
|
126
|
-
|
|
127
|
-
if (useCursorPagination) {
|
|
128
|
-
lastCursor = result.nextCursor ?? null;
|
|
129
|
-
hasMorePages = result.hasMore ?? false;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!disableTotalCount) {
|
|
133
|
-
totalRecordCount = result.total || 0;
|
|
134
|
-
totalPageCount = result.totalPages || Math.ceil(totalRecordCount / rowsPerPage);
|
|
135
|
-
} else {
|
|
136
|
-
hasMorePages = result.data && result.data.length === rowsPerPage;
|
|
137
|
-
}
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error("Error loading data:", error);
|
|
140
|
-
dataResult = [];
|
|
141
|
-
totalRecordCount = 0;
|
|
142
|
-
totalPageCount = 0;
|
|
143
|
-
hasMorePages = false;
|
|
144
|
-
throw error;
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const reset = async (): Promise<void> => {
|
|
149
|
-
currentPage = 1;
|
|
150
|
-
await load();
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const moveNextPage = async (): Promise<void> => {
|
|
154
|
-
if (hasNextPage()) {
|
|
155
|
-
currentPage++;
|
|
156
|
-
await load();
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const movePrevPage = async (): Promise<void> => {
|
|
161
|
-
if (hasPrevPage()) {
|
|
162
|
-
currentPage--;
|
|
163
|
-
await load();
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const moveFirstPage = async (): Promise<void> => {
|
|
168
|
-
currentPage = 1;
|
|
169
|
-
await load();
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const moveLastPage = async (): Promise<void> => {
|
|
173
|
-
if (disableTotalCount) {
|
|
174
|
-
console.warn("moveLastPage is not available when total count is disabled");
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
if (totalPageCount > 0) {
|
|
178
|
-
currentPage = totalPageCount;
|
|
179
|
-
await load();
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const moveToPage = async (page: number): Promise<void> => {
|
|
184
|
-
if (page >= 1 && page <= totalPageCount) {
|
|
185
|
-
currentPage = page;
|
|
186
|
-
await load();
|
|
187
|
-
} else {
|
|
188
|
-
console.warn(`Invalid page number: ${page}. Must be between 1 and ${totalPageCount}`);
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const setFilter = async (filter: Record<string, any>): Promise<void> => {
|
|
193
|
-
customFilter = { ...filter };
|
|
194
|
-
currentPage = 1;
|
|
195
|
-
await load();
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const resetFilter = async (): Promise<void> => {
|
|
199
|
-
customFilter = {};
|
|
200
|
-
currentPage = 1;
|
|
201
|
-
await load();
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const doSearch = async (text: string): Promise<void> => {
|
|
205
|
-
searchText = text;
|
|
206
|
-
currentPage = 1;
|
|
207
|
-
await load();
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const sortAsc = async (column: string): Promise<void> => {
|
|
211
|
-
sortColumn = column;
|
|
212
|
-
sortDirection = "asc";
|
|
213
|
-
currentPage = 1;
|
|
214
|
-
await load();
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const sortDesc = async (column: string): Promise<void> => {
|
|
218
|
-
sortColumn = column;
|
|
219
|
-
sortDirection = "desc";
|
|
220
|
-
currentPage = 1;
|
|
221
|
-
await load();
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
const setRowsPerPage = async (rows: number): Promise<void> => {
|
|
225
|
-
if (rows > 0) {
|
|
226
|
-
rowsPerPage = rows;
|
|
227
|
-
currentPage = 1;
|
|
228
|
-
await load();
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const hasNextPage = (): boolean => {
|
|
233
|
-
if (disableTotalCount || useCursorPagination) return hasMorePages;
|
|
234
|
-
return currentPage < totalPageCount;
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const hasPrevPage = (): boolean => currentPage > 1;
|
|
238
|
-
|
|
239
|
-
const getTotalPageCount = (): number => totalPageCount;
|
|
240
|
-
const getTotalRecordCount = (): number => totalRecordCount;
|
|
241
|
-
const getCurrentPage = (): number => currentPage;
|
|
242
|
-
const getCurrentSortedColumn = (): string | null => sortColumn;
|
|
243
|
-
const getCurrentSortDirection = (): "asc" | "desc" | null => sortDirection;
|
|
244
|
-
const getRowsPerPage = (): number => rowsPerPage;
|
|
245
|
-
const getSearchText = (): string => searchText;
|
|
246
|
-
const getCustomFilter = (): Record<string, any> => ({ ...customFilter });
|
|
247
|
-
|
|
248
|
-
return {
|
|
249
|
-
getData,
|
|
250
|
-
load,
|
|
251
|
-
reset,
|
|
252
|
-
moveNextPage,
|
|
253
|
-
movePrevPage,
|
|
254
|
-
moveFirstPage,
|
|
255
|
-
moveLastPage,
|
|
256
|
-
moveToPage,
|
|
257
|
-
setFilter,
|
|
258
|
-
resetFilter,
|
|
259
|
-
doSearch,
|
|
260
|
-
sortAsc,
|
|
261
|
-
sortDesc,
|
|
262
|
-
setRowsPerPage,
|
|
263
|
-
hasNextPage,
|
|
264
|
-
hasPrevPage,
|
|
265
|
-
getTotalPageCount,
|
|
266
|
-
getTotalRecordCount,
|
|
267
|
-
getCurrentPage,
|
|
268
|
-
getCurrentSortedColumn,
|
|
269
|
-
getCurrentSortDirection,
|
|
270
|
-
getRowsPerPage,
|
|
271
|
-
getSearchText,
|
|
272
|
-
getCustomFilter,
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
export default ListHandler;
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState, useEffect, useRef, ReactNode } from "react";
|
|
2
|
-
import { ColumnDefinition } from "./DataTable";
|
|
3
|
-
import { ListHandlerConfig, ListActionHandler } from "./ListHandler";
|
|
4
|
-
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// INTERFACES
|
|
7
|
-
// ============================================================================
|
|
8
|
-
|
|
9
|
-
export interface DataConfig {
|
|
10
|
-
api: string;
|
|
11
|
-
params?: Record<string, any>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface TableContextValue {
|
|
15
|
-
listHandler: ListActionHandler | null;
|
|
16
|
-
columns: ColumnDefinition[];
|
|
17
|
-
setColumns: React.Dispatch<React.SetStateAction<ColumnDefinition[]>>;
|
|
18
|
-
rows: any[];
|
|
19
|
-
setRows: React.Dispatch<React.SetStateAction<any[]>>;
|
|
20
|
-
loading: boolean;
|
|
21
|
-
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// ============================================================================
|
|
25
|
-
// CONTEXT
|
|
26
|
-
// ============================================================================
|
|
27
|
-
|
|
28
|
-
const TableContext = createContext<TableContextValue | null>(null);
|
|
29
|
-
|
|
30
|
-
export const useTableContext = (): TableContextValue => {
|
|
31
|
-
const ctx = useContext(TableContext);
|
|
32
|
-
if (!ctx) throw new Error("useTableContext must be used inside <TableProvider>");
|
|
33
|
-
return ctx;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// PROVIDER PROPS
|
|
38
|
-
// ============================================================================
|
|
39
|
-
|
|
40
|
-
export interface TableProviderProps {
|
|
41
|
-
data: DataConfig;
|
|
42
|
-
columns?: ColumnDefinition[];
|
|
43
|
-
rowsPerPage?: number;
|
|
44
|
-
disableTotalCount?: boolean;
|
|
45
|
-
useCursorPagination?: boolean;
|
|
46
|
-
children: ReactNode;
|
|
47
|
-
listHandlerFactory: (config: ListHandlerConfig) => ListActionHandler;
|
|
48
|
-
// Resolved by DataList before being passed here — no hooks needed inside Provider
|
|
49
|
-
tenant: string;
|
|
50
|
-
module: string;
|
|
51
|
-
resolvedParams: Record<string, any> | undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// PROVIDER
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
|
-
export const TableProvider: React.FC<TableProviderProps> = ({
|
|
59
|
-
data,
|
|
60
|
-
columns: initialColumns = [],
|
|
61
|
-
rowsPerPage = 20,
|
|
62
|
-
disableTotalCount = true,
|
|
63
|
-
useCursorPagination = false,
|
|
64
|
-
children,
|
|
65
|
-
listHandlerFactory,
|
|
66
|
-
tenant,
|
|
67
|
-
module,
|
|
68
|
-
resolvedParams,
|
|
69
|
-
}) => {
|
|
70
|
-
const colIds = initialColumns.map((c) => c.id ?? c.expr ?? "").filter(Boolean);
|
|
71
|
-
|
|
72
|
-
// ListHandler created ONCE via ref.
|
|
73
|
-
// resolvedParams already has all placeholders replaced — passed in from DataList.
|
|
74
|
-
const listHandlerRef = useRef<ListActionHandler | null>(null);
|
|
75
|
-
if (listHandlerRef.current === null) {
|
|
76
|
-
listHandlerRef.current = listHandlerFactory({
|
|
77
|
-
tenant,
|
|
78
|
-
module,
|
|
79
|
-
api: data.api,
|
|
80
|
-
params: resolvedParams,
|
|
81
|
-
cols: colIds,
|
|
82
|
-
rowsPerPage,
|
|
83
|
-
disableTotalCount,
|
|
84
|
-
useCursorPagination,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const [columns, setColumns] = useState<ColumnDefinition[]>(initialColumns);
|
|
89
|
-
const [rows, setRows] = useState<any[]>([]);
|
|
90
|
-
const [loading, setLoading] = useState(false);
|
|
91
|
-
|
|
92
|
-
// Initial data load on mount
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
const lh = listHandlerRef.current;
|
|
95
|
-
if (!lh) return;
|
|
96
|
-
|
|
97
|
-
setLoading(true);
|
|
98
|
-
lh.load()
|
|
99
|
-
.then(() => setRows([...lh.getData()]))
|
|
100
|
-
.catch((err) => console.error("TableProvider initial load failed:", err))
|
|
101
|
-
.finally(() => setLoading(false));
|
|
102
|
-
}, []);
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<TableContext.Provider
|
|
106
|
-
value={{
|
|
107
|
-
listHandler: listHandlerRef.current,
|
|
108
|
-
columns,
|
|
109
|
-
setColumns,
|
|
110
|
-
rows,
|
|
111
|
-
setRows,
|
|
112
|
-
loading,
|
|
113
|
-
setLoading,
|
|
114
|
-
}}
|
|
115
|
-
>
|
|
116
|
-
{children}
|
|
117
|
-
</TableContext.Provider>
|
|
118
|
-
);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export type { ListHandlerConfig };
|
|
122
|
-
export default TableContext;
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useApp } from "../../core/AppContext";
|
|
3
|
-
import { DynamicComponent } from "../../core/DynamicComponent";
|
|
4
|
-
import { usePageContext } from "../../core/PageContext";
|
|
5
|
-
import useDependHandler from "../../core/UIDependHandler";
|
|
6
|
-
import UIComponent, { UIComponentProps } from "../common/UIComponent";
|
|
7
|
-
|
|
8
|
-
interface ComponentViewProps extends UIComponentProps {
|
|
9
|
-
// use for dynamic component rendering
|
|
10
|
-
id?: string;
|
|
11
|
-
|
|
12
|
-
// use to get value from page context
|
|
13
|
-
depends?: string;
|
|
14
|
-
|
|
15
|
-
// use to statically set the actual component
|
|
16
|
-
component?: React.ReactNode;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const ComponentView = (props: ComponentViewProps) => {
|
|
20
|
-
const { id, name, depends, component } = props ?? {};
|
|
21
|
-
const [comp, setComp] = useState<React.ReactNode | null>(null);
|
|
22
|
-
const [error, setError] = useState("");
|
|
23
|
-
const [forceUpdate, setForceUpdate] = useState({});
|
|
24
|
-
const { getComponentCache } = useApp();
|
|
25
|
-
const pageContext = usePageContext();
|
|
26
|
-
|
|
27
|
-
const getPreferredName = () => {
|
|
28
|
-
if (name == null || name.trim() === "") {
|
|
29
|
-
return depends;
|
|
30
|
-
}
|
|
31
|
-
return name;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const loadComponentById = async () => {
|
|
35
|
-
if (id != null && id.trim() !== "") {
|
|
36
|
-
const cache = await getComponentCache(id);
|
|
37
|
-
const template: React.ReactNode = cache.template;
|
|
38
|
-
setComp(template);
|
|
39
|
-
return template;
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const loadComponentByName = () => {
|
|
45
|
-
const preferredName = getPreferredName();
|
|
46
|
-
if (preferredName != null && preferredName.trim() !== "") {
|
|
47
|
-
const info = pageContext?.get(preferredName) as { component: string; attr?: Record<string, any> };
|
|
48
|
-
const { component, attr = {} } = info ?? {};
|
|
49
|
-
|
|
50
|
-
if (component == null || component.trim() === "") {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return <DynamicComponent config={{ component, attr }} />;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const loadStaticComponent = () => {
|
|
60
|
-
if (typeof component === "string") {
|
|
61
|
-
return <DynamicComponent config={{ component }} />;
|
|
62
|
-
}
|
|
63
|
-
return component;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const onRefresh = () => {
|
|
67
|
-
setForceUpdate({});
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
useDependHandler({ name: depends, onRefresh });
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
const prefName = name ?? depends ?? "";
|
|
74
|
-
|
|
75
|
-
const tmpl = loadStaticComponent();
|
|
76
|
-
if (tmpl != null) {
|
|
77
|
-
setComp(tmpl);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
loadComponentById()
|
|
82
|
-
.then((template) => {
|
|
83
|
-
if (template != null) {
|
|
84
|
-
setComp(template);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const tmpl2 = loadComponentByName();
|
|
89
|
-
if (tmpl2 != null) {
|
|
90
|
-
setComp(tmpl2);
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
.catch((err) => {
|
|
94
|
-
console.log("error", err);
|
|
95
|
-
setError(err.message);
|
|
96
|
-
});
|
|
97
|
-
}, [forceUpdate]);
|
|
98
|
-
|
|
99
|
-
return <UIComponent {...props}>{error ? <div>{error}</div> : <div>{comp}</div>}</UIComponent>;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export default ComponentView;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import VPanel from "../../layouts/VPanel";
|
|
2
|
-
import { renderListDef } from "../../lib/utils/SectionProvider";
|
|
3
|
-
|
|
4
|
-
const FilterView = (props: Record<string, any>) => {
|
|
5
|
-
const { items = [] } = props ?? {};
|
|
6
|
-
|
|
7
|
-
if (items.length === 0) return null;
|
|
8
|
-
|
|
9
|
-
const buildPanel = () => {
|
|
10
|
-
const comps = renderListDef(items);
|
|
11
|
-
return <VPanel gap={8}>{comps}</VPanel>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<>
|
|
16
|
-
<div>{buildPanel()}</div>
|
|
17
|
-
</>
|
|
18
|
-
);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export default FilterView;
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { localAPI } from "@ramesesinc/lib/local-api";
|
|
2
|
-
import React, { useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { useApp } from "../../core/AppContext";
|
|
4
|
-
import { DynamicComponent } from "../../core/DynamicComponent";
|
|
5
|
-
import useDependHandler from "../../core/UIDependHandler";
|
|
6
|
-
import UIComponent from "../common/UIComponent";
|
|
7
|
-
|
|
8
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
-
// Types
|
|
10
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
type HtmlFormProps = {
|
|
13
|
-
label?: string;
|
|
14
|
-
depends?: string;
|
|
15
|
-
templateid?: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type SentinelConfig = {
|
|
19
|
-
component: string;
|
|
20
|
-
attr: any;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
-
// Attr Parser
|
|
25
|
-
// Handles both strict JSON {"key":"value"} and JS object literals {key: "value"}
|
|
26
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
function parseAttr(attrStr: string | null): any {
|
|
29
|
-
if (!attrStr) return {};
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(attrStr);
|
|
32
|
-
} catch {
|
|
33
|
-
try {
|
|
34
|
-
// eslint-disable-next-line no-new-func
|
|
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);
|
|
38
|
-
} catch {
|
|
39
|
-
console.warn("HtmlForm: could not parse attr →", attrStr);
|
|
40
|
-
return {};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
-
// DOM Attribute → React Prop mapper
|
|
47
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
const ATTR_MAP: Record<string, string> = {
|
|
50
|
-
class: "className",
|
|
51
|
-
colspan: "colSpan",
|
|
52
|
-
rowspan: "rowSpan",
|
|
53
|
-
for: "htmlFor",
|
|
54
|
-
tabindex: "tabIndex",
|
|
55
|
-
readonly: "readOnly",
|
|
56
|
-
maxlength: "maxLength",
|
|
57
|
-
cellpadding: "cellPadding",
|
|
58
|
-
cellspacing: "cellSpacing",
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
function styleStringToObject(styleStr: string): Record<string, string> {
|
|
62
|
-
const style: Record<string, string> = {};
|
|
63
|
-
styleStr.split(";").forEach((rule) => {
|
|
64
|
-
const [prop, val] = rule.split(":").map((s) => s.trim());
|
|
65
|
-
if (prop && val) {
|
|
66
|
-
const camel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
67
|
-
style[camel] = val;
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
return style;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function domAttrsToProps(el: Element): Record<string, any> {
|
|
74
|
-
const props: Record<string, any> = {};
|
|
75
|
-
for (const attr of Array.from(el.attributes)) {
|
|
76
|
-
if (["component", "attr"].includes(attr.name)) continue;
|
|
77
|
-
const reactName = ATTR_MAP[attr.name] ?? attr.name;
|
|
78
|
-
props[reactName] = attr.name === "style" ? styleStringToObject(attr.value) : attr.value;
|
|
79
|
-
}
|
|
80
|
-
return props;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
-
// Table structural elements that cannot contain whitespace text nodes
|
|
85
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
const TABLE_ELEMENTS = new Set(["table", "thead", "tbody", "tfoot", "tr", "colgroup"]);
|
|
88
|
-
|
|
89
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
90
|
-
// DOM Walker
|
|
91
|
-
// Turns a DOM tree into a React element tree.
|
|
92
|
-
// Sentinel <span component="X" attr="..."> becomes <DynamicComponent config={...} />
|
|
93
|
-
// Everything else becomes a plain React element.
|
|
94
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
let _keyIndex = 0;
|
|
97
|
-
const nextKey = () => `hf_${_keyIndex++}`;
|
|
98
|
-
|
|
99
|
-
function walkNode(node: ChildNode, parentTag?: string): React.ReactNode {
|
|
100
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
101
|
-
// Whitespace-only text nodes are invalid inside table structural elements
|
|
102
|
-
// and cause React's validateDOMNesting warning.
|
|
103
|
-
if (parentTag && TABLE_ELEMENTS.has(parentTag) && !node.textContent?.trim()) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
return node.textContent || null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
110
|
-
const el = node as Element;
|
|
111
|
-
const tag = el.tagName.toLowerCase();
|
|
112
|
-
const componentName = el.getAttribute("component");
|
|
113
|
-
|
|
114
|
-
// ── Sentinel span → DynamicComponent ──────────────────────────────────
|
|
115
|
-
if (tag === "span" && componentName) {
|
|
116
|
-
const attr: any = parseAttr(el.getAttribute("attr"));
|
|
117
|
-
const config: SentinelConfig = { component: componentName, attr };
|
|
118
|
-
return <DynamicComponent key={nextKey()} config={config} />;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ── Regular element → recurse ──────────────────────────────────────────
|
|
122
|
-
const props = { ...domAttrsToProps(el), key: nextKey() };
|
|
123
|
-
const children: React.ReactNode[] = Array.from(el.childNodes)
|
|
124
|
-
.map((child) => walkNode(child, tag)) // pass current tag as parentTag
|
|
125
|
-
.filter((n) => n !== null && n !== undefined);
|
|
126
|
-
|
|
127
|
-
return children.length > 0 ? React.createElement(tag, props, ...children) : React.createElement(tag, props);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
134
|
-
// HtmlForm
|
|
135
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
136
|
-
|
|
137
|
-
const HtmlForm = (props: HtmlFormProps) => {
|
|
138
|
-
const { depends, templateid } = props ?? {};
|
|
139
|
-
|
|
140
|
-
const [htmlTemplate, setHtmlTemplate] = useState("");
|
|
141
|
-
|
|
142
|
-
const { tenant, module } = useApp();
|
|
143
|
-
|
|
144
|
-
// ── Load raw HTML from html_forms ──────────────────────────────────────────
|
|
145
|
-
const loadHtmlContent = async (tempid: string) => {
|
|
146
|
-
try {
|
|
147
|
-
const record = await localAPI.useMgmt(tenant!, module!).get("html_templates", tempid);
|
|
148
|
-
setHtmlTemplate(record?.htmlCode ?? "");
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error("HtmlForm: error loading html_forms →", error);
|
|
151
|
-
setHtmlTemplate("");
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
useDependHandler({ name: depends, onRefresh: () => {} });
|
|
156
|
-
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
if (templateid?.trim()) loadHtmlContent(templateid);
|
|
159
|
-
}, [templateid]);
|
|
160
|
-
|
|
161
|
-
// ── Walk DOM → React tree (only re-runs when htmlTemplate changes) ─────────
|
|
162
|
-
const reactTree = useMemo(() => {
|
|
163
|
-
if (!htmlTemplate) return null;
|
|
164
|
-
_keyIndex = 0;
|
|
165
|
-
const doc = new DOMParser().parseFromString(htmlTemplate, "text/html");
|
|
166
|
-
return Array.from(doc.body.childNodes).map((child) => walkNode(child));
|
|
167
|
-
}, [htmlTemplate]);
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<UIComponent {...(props ?? {})}>
|
|
171
|
-
<div className="h-[calc(100vh-80px)] overflow-y-auto overflow-x-auto">{reactTree}</div>
|
|
172
|
-
</UIComponent>
|
|
173
|
-
);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
export default HtmlForm;
|