@ramesesinc/platform-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/dist/components/action/AlertMessage.d.ts +8 -0
  2. package/dist/components/action/AlertMessage.js +19 -0
  3. package/dist/components/action/AlertMessage.tsx +38 -0
  4. package/dist/components/action/Button.d.ts +21 -0
  5. package/dist/components/action/Button.js +139 -0
  6. package/dist/components/action/Button.tsx +230 -0
  7. package/dist/components/action/CancelEdit.d.ts +9 -0
  8. package/dist/components/action/CancelEdit.js +21 -0
  9. package/dist/components/action/CancelEdit.tsx +40 -0
  10. package/dist/components/action/DeleteData.d.ts +13 -0
  11. package/dist/components/action/DeleteData.js +43 -0
  12. package/dist/components/action/DeleteData.tsx +73 -0
  13. package/dist/components/action/Edit.d.ts +9 -0
  14. package/dist/components/action/Edit.js +21 -0
  15. package/dist/components/action/Edit.tsx +40 -0
  16. package/dist/components/action/LookupPage.d.ts +16 -0
  17. package/dist/components/action/LookupPage.js +62 -0
  18. package/dist/components/action/LookupPage.tsx +113 -0
  19. package/dist/components/action/ProcessRunner.d.ts +62 -0
  20. package/dist/components/action/ProcessRunner.js +156 -0
  21. package/dist/components/action/ProcessRunner.tsx +337 -0
  22. package/dist/components/action/Refresh.d.ts +7 -0
  23. package/dist/components/action/Refresh.js +17 -0
  24. package/dist/components/action/Refresh.tsx +35 -0
  25. package/dist/components/action/SaveData.d.ts +10 -0
  26. package/dist/components/action/SaveData.js +54 -0
  27. package/dist/components/action/SaveData.tsx +74 -0
  28. package/dist/components/action/SelectData.d.ts +8 -0
  29. package/dist/components/action/SelectData.js +29 -0
  30. package/dist/components/action/SelectData.tsx +47 -0
  31. package/dist/components/action/Undo.d.ts +9 -0
  32. package/dist/components/action/Undo.js +31 -0
  33. package/dist/components/action/Undo.tsx +50 -0
  34. package/dist/components/action/UpdateContext.d.ts +9 -0
  35. package/dist/components/action/UpdateContext.js +21 -0
  36. package/dist/components/action/UpdateContext.tsx +40 -0
  37. package/dist/components/action/UpdateData.d.ts +9 -0
  38. package/dist/components/action/UpdateData.js +33 -0
  39. package/dist/components/action/UpdateData.tsx +49 -0
  40. package/dist/components/action/ViewBackPage.d.ts +9 -0
  41. package/dist/components/action/ViewBackPage.js +19 -0
  42. package/dist/components/action/ViewBackPage.tsx +46 -0
  43. package/dist/components/action/ViewPage.d.ts +14 -0
  44. package/dist/components/action/ViewPage.js +88 -0
  45. package/dist/components/action/ViewPage.tsx +141 -0
  46. package/dist/components/common/UIComponent.d.ts +11 -0
  47. package/dist/components/common/UIComponent.js +52 -0
  48. package/dist/components/common/UIComponent.tsx +84 -0
  49. package/dist/components/common/UIInput.d.ts +12 -0
  50. package/dist/components/common/UIInput.js +37 -0
  51. package/dist/components/common/UIInput.tsx +49 -0
  52. package/dist/components/common/UIMenu.d.ts +23 -0
  53. package/dist/components/common/UIMenu.js +61 -0
  54. package/dist/components/common/UIMenu.tsx +91 -0
  55. package/dist/components/index.d.ts +37 -0
  56. package/dist/components/index.js +44 -0
  57. package/dist/components/index.ts +51 -0
  58. package/dist/components/input/CodeEditor.d.ts +12 -0
  59. package/dist/components/input/CodeEditor.js +132 -0
  60. package/dist/components/input/CodeEditor.tsx +188 -0
  61. package/dist/components/input/DateField.d.ts +9 -0
  62. package/dist/components/input/DateField.js +140 -0
  63. package/dist/components/input/DateField.tsx +274 -0
  64. package/dist/components/input/DayPicker.d.ts +2 -0
  65. package/dist/components/input/DayPicker.js +5 -0
  66. package/dist/components/input/DayPicker.tsx +5 -0
  67. package/dist/components/input/HtmlCode.d.ts +8 -0
  68. package/dist/components/input/HtmlCode.js +157 -0
  69. package/dist/components/input/HtmlCode.tsx +203 -0
  70. package/dist/components/input/JsonCode.d.ts +9 -0
  71. package/dist/components/input/JsonCode.js +159 -0
  72. package/dist/components/input/JsonCode.tsx +205 -0
  73. package/dist/components/input/MonthPicker.d.ts +2 -0
  74. package/dist/components/input/MonthPicker.js +5 -0
  75. package/dist/components/input/MonthPicker.tsx +5 -0
  76. package/dist/components/input/ScriptCode.d.ts +8 -0
  77. package/dist/components/input/ScriptCode.js +153 -0
  78. package/dist/components/input/ScriptCode.tsx +195 -0
  79. package/dist/components/input/Select.d.ts +14 -0
  80. package/dist/components/input/Select.js +40 -0
  81. package/dist/components/input/Select.tsx +78 -0
  82. package/dist/components/input/SqlCode.d.ts +8 -0
  83. package/dist/components/input/SqlCode.js +121 -0
  84. package/dist/components/input/SqlCode.tsx +162 -0
  85. package/dist/components/input/StringDecision.d.ts +2 -0
  86. package/dist/components/input/StringDecision.js +34 -0
  87. package/dist/components/input/StringDecision.tsx +64 -0
  88. package/dist/components/input/Text.d.ts +7 -0
  89. package/dist/components/input/Text.js +39 -0
  90. package/dist/components/input/Text.tsx +57 -0
  91. package/dist/components/input/YearPicker.d.ts +8 -0
  92. package/dist/components/input/YearPicker.js +44 -0
  93. package/dist/components/input/YearPicker.tsx +81 -0
  94. package/dist/components/list/IconMenu.d.ts +14 -0
  95. package/dist/components/list/IconMenu.js +72 -0
  96. package/dist/components/list/IconMenu.tsx +115 -0
  97. package/dist/components/list/TabMenu.d.ts +10 -0
  98. package/dist/components/list/TabMenu.js +72 -0
  99. package/dist/components/list/TabMenu.tsx +127 -0
  100. package/dist/components/list/TreeMenu.d.ts +14 -0
  101. package/dist/components/list/TreeMenu.js +207 -0
  102. package/dist/components/list/TreeMenu.tsx +279 -0
  103. package/dist/components/list/TxnTaskList.d.ts +2 -0
  104. package/dist/components/list/TxnTaskList.js +77 -0
  105. package/dist/components/list/TxnTaskList.tsx +198 -0
  106. package/dist/components/output/Label.d.ts +8 -0
  107. package/dist/components/output/Label.js +33 -0
  108. package/dist/components/output/Label.tsx +51 -0
  109. package/dist/components/table/DataList.d.ts +88 -0
  110. package/dist/components/table/DataList.js +361 -0
  111. package/dist/components/table/DataList.tsx +782 -0
  112. package/dist/components/table/DataTable.d.ts +46 -0
  113. package/dist/components/table/DataTable.js +253 -0
  114. package/dist/components/table/DataTable.tsx +572 -0
  115. package/dist/components/table/ListHandler.d.ts +42 -0
  116. package/dist/components/table/ListHandler.js +197 -0
  117. package/dist/components/table/ListHandler.ts +276 -0
  118. package/dist/components/table/TableContext.d.ts +33 -0
  119. package/dist/components/table/TableContext.js +57 -0
  120. package/dist/components/table/TableContext.tsx +122 -0
  121. package/dist/components/view/ComponentView.d.ts +8 -0
  122. package/dist/components/view/ComponentView.js +78 -0
  123. package/dist/components/view/ComponentView.tsx +102 -0
  124. package/dist/components/view/FilterView.d.ts +2 -0
  125. package/dist/components/view/FilterView.js +14 -0
  126. package/dist/components/view/FilterView.tsx +21 -0
  127. package/dist/components/view/HtmlForm.d.ts +7 -0
  128. package/dist/components/view/HtmlForm.js +145 -0
  129. package/dist/components/view/HtmlForm.tsx +186 -0
  130. package/dist/components/view/HtmlView.d.ts +9 -0
  131. package/dist/components/view/HtmlView.js +85 -0
  132. package/dist/components/view/HtmlView.tsx +114 -0
  133. package/dist/components/view/IFrameView.d.ts +2 -0
  134. package/dist/components/view/IFrameView.js +34 -0
  135. package/dist/components/view/IFrameView.tsx +48 -0
  136. package/dist/components/view/Modal.d.ts +14 -0
  137. package/dist/components/view/Modal.js +28 -0
  138. package/dist/components/view/Modal.tsx +72 -0
  139. package/dist/components/view/PageView.d.ts +9 -0
  140. package/dist/components/view/PageView.js +90 -0
  141. package/dist/components/view/PageView.tsx +131 -0
  142. package/dist/components/view/PopupView.d.ts +9 -0
  143. package/dist/components/view/PopupView.js +91 -0
  144. package/dist/components/view/PopupView.tsx +160 -0
  145. package/dist/components/view/RootView.d.ts +8 -0
  146. package/dist/components/view/RootView.js +75 -0
  147. package/dist/components/view/RootView.tsx +109 -0
  148. package/dist/components/view/WizardView.d.ts +6 -0
  149. package/dist/components/view/WizardView.js +34 -0
  150. package/dist/components/view/WizardView.tsx +48 -0
  151. package/dist/core/AppContext.d.ts +25 -0
  152. package/dist/core/AppContext.js +159 -0
  153. package/dist/core/AuthContext.d.ts +13 -0
  154. package/dist/core/AuthContext.js +80 -0
  155. package/dist/core/ComponentCache.d.ts +15 -0
  156. package/dist/core/ComponentCache.js +25 -0
  157. package/dist/core/DataContext.d.ts +20 -0
  158. package/dist/core/DataContext.js +41 -0
  159. package/dist/core/DynamicComponent.d.ts +12 -0
  160. package/dist/core/DynamicComponent.js +30 -0
  161. package/dist/core/DynamicIcon.d.ts +6 -0
  162. package/dist/core/DynamicIcon.js +48 -0
  163. package/dist/core/DynamicTemplate.d.ts +4 -0
  164. package/dist/core/DynamicTemplate.js +17 -0
  165. package/dist/core/ErrorMessage.d.ts +5 -0
  166. package/dist/core/ErrorMessage.js +7 -0
  167. package/dist/core/EventHandler.d.ts +3 -0
  168. package/dist/core/EventHandler.js +1 -0
  169. package/dist/core/HtmlCache.d.ts +6 -0
  170. package/dist/core/HtmlCache.js +17 -0
  171. package/dist/core/Page.d.ts +6 -0
  172. package/dist/core/Page.js +141 -0
  173. package/dist/core/PageCache.d.ts +15 -0
  174. package/dist/core/PageCache.js +26 -0
  175. package/dist/core/PageContext.d.ts +49 -0
  176. package/dist/core/PageContext.js +207 -0
  177. package/dist/core/PageRegistry.d.ts +7 -0
  178. package/dist/core/PageRegistry.js +11 -0
  179. package/dist/core/PageViewContext.d.ts +45 -0
  180. package/dist/core/PageViewContext.js +277 -0
  181. package/dist/core/Panel.d.ts +16 -0
  182. package/dist/core/Panel.js +39 -0
  183. package/dist/core/RowContext.d.ts +11 -0
  184. package/dist/core/RowContext.js +16 -0
  185. package/dist/core/StepHandler.d.ts +9 -0
  186. package/dist/core/StepHandler.js +39 -0
  187. package/dist/core/UIDependHandler.d.ts +6 -0
  188. package/dist/core/UIDependHandler.js +31 -0
  189. package/dist/core/auth/index.d.ts +18 -0
  190. package/dist/core/auth/index.js +187 -0
  191. package/dist/core/auth/session.d.ts +4 -0
  192. package/dist/core/auth/session.js +45 -0
  193. package/dist/core/index.d.ts +3 -0
  194. package/dist/core/index.js +1 -0
  195. package/dist/core/page-helper.d.ts +27 -0
  196. package/dist/core/page-helper.js +47 -0
  197. package/dist/hooks/useUrlParams.d.ts +11 -0
  198. package/dist/hooks/useUrlParams.js +164 -0
  199. package/dist/index.d.ts +65 -0
  200. package/dist/index.js +69 -0
  201. package/dist/layouts/BorderLayout.d.ts +14 -0
  202. package/dist/layouts/BorderLayout.js +8 -0
  203. package/dist/layouts/CardLayout.d.ts +27 -0
  204. package/dist/layouts/CardLayout.js +36 -0
  205. package/dist/layouts/CenterLayout.d.ts +8 -0
  206. package/dist/layouts/CenterLayout.js +6 -0
  207. package/dist/layouts/GridLayout.d.ts +9 -0
  208. package/dist/layouts/GridLayout.js +5 -0
  209. package/dist/layouts/HPanel.d.ts +11 -0
  210. package/dist/layouts/HPanel.js +8 -0
  211. package/dist/layouts/HorizontalLayout.d.ts +11 -0
  212. package/dist/layouts/HorizontalLayout.js +6 -0
  213. package/dist/layouts/MainLayout.d.ts +6 -0
  214. package/dist/layouts/MainLayout.js +5 -0
  215. package/dist/layouts/PageLayout.d.ts +11 -0
  216. package/dist/layouts/PageLayout.js +6 -0
  217. package/dist/layouts/VPanel.d.ts +9 -0
  218. package/dist/layouts/VPanel.js +7 -0
  219. package/dist/layouts/XLayout.d.ts +10 -0
  220. package/dist/layouts/XLayout.js +10 -0
  221. package/dist/layouts/YLayout.d.ts +10 -0
  222. package/dist/layouts/YLayout.js +10 -0
  223. package/dist/lib/PlatformRoute.d.ts +5 -0
  224. package/dist/lib/PlatformRoute.js +288 -0
  225. package/dist/lib/WebPlatform.d.ts +13 -0
  226. package/dist/lib/WebPlatform.js +124 -0
  227. package/dist/lib/components/ActivityBar.d.ts +13 -0
  228. package/dist/lib/components/ActivityBar.js +39 -0
  229. package/dist/lib/components/CopyButton.d.ts +8 -0
  230. package/dist/lib/components/CopyButton.js +50 -0
  231. package/dist/lib/components/Header.d.ts +2 -0
  232. package/dist/lib/components/Header.js +74 -0
  233. package/dist/lib/components/QuickActionMenu.d.ts +18 -0
  234. package/dist/lib/components/QuickActionMenu.js +50 -0
  235. package/dist/lib/components/UserButton.d.ts +11 -0
  236. package/dist/lib/components/UserButton.js +66 -0
  237. package/dist/lib/layouts/BorderLayout.tsx +31 -0
  238. package/dist/lib/layouts/CardLayout.tsx +73 -0
  239. package/dist/lib/layouts/CenterLayout.tsx +20 -0
  240. package/dist/lib/layouts/GridLayout.tsx +20 -0
  241. package/dist/lib/layouts/HPanel.tsx +31 -0
  242. package/dist/lib/layouts/HorizontalLayout.tsx +29 -0
  243. package/dist/lib/layouts/MainLayout.tsx +16 -0
  244. package/dist/lib/layouts/PageLayout.tsx +29 -0
  245. package/dist/lib/layouts/VPanel.tsx +27 -0
  246. package/dist/lib/layouts/XLayout.tsx +29 -0
  247. package/dist/lib/layouts/YLayout.tsx +29 -0
  248. package/dist/lib/utils/BeanUtils.d.ts +3 -0
  249. package/dist/lib/utils/BeanUtils.js +75 -0
  250. package/dist/lib/utils/ComponentLoader.d.ts +13 -0
  251. package/dist/lib/utils/ComponentLoader.js +26 -0
  252. package/dist/lib/utils/ExprUtil.d.ts +7 -0
  253. package/dist/lib/utils/ExprUtil.js +44 -0
  254. package/dist/lib/utils/PageUtils.d.ts +6 -0
  255. package/dist/lib/utils/PageUtils.js +121 -0
  256. package/dist/lib/utils/ResourceLoader.d.ts +11 -0
  257. package/dist/lib/utils/ResourceLoader.js +37 -0
  258. package/dist/lib/utils/SectionProvider.d.ts +5 -0
  259. package/dist/lib/utils/SectionProvider.js +39 -0
  260. package/dist/lib/utils/initResourceLoader.d.ts +0 -0
  261. package/dist/lib/utils/initResourceLoader.js +95 -0
  262. package/dist/styles/index.css +38 -0
  263. package/dist/templates/BasicTemplate.d.ts +2 -0
  264. package/dist/templates/BasicTemplate.js +14 -0
  265. package/dist/templates/CrudFormTemplate.d.ts +2 -0
  266. package/dist/templates/CrudFormTemplate.js +38 -0
  267. package/dist/templates/DataListTemplate.d.ts +2 -0
  268. package/dist/templates/DataListTemplate.js +13 -0
  269. package/dist/templates/ExplorerTemplate.d.ts +10 -0
  270. package/dist/templates/ExplorerTemplate.js +17 -0
  271. package/dist/templates/TxnFormTemplate.d.ts +2 -0
  272. package/dist/templates/TxnFormTemplate.js +66 -0
  273. package/dist/templates/WizardTemplate.d.ts +9 -0
  274. package/dist/templates/WizardTemplate.js +37 -0
  275. package/dist/templates/index.d.ts +5 -0
  276. package/dist/templates/index.js +5 -0
  277. package/dist/types/action.d.ts +8 -0
  278. package/dist/types/action.js +1 -0
  279. package/dist/types/component.d.ts +18 -0
  280. package/dist/types/component.js +1 -0
  281. package/dist/types/list.d.ts +14 -0
  282. package/dist/types/list.js +1 -0
  283. package/dist/types/template.d.ts +6 -0
  284. package/dist/types/template.js +1 -0
  285. package/package.json +56 -0
@@ -0,0 +1,572 @@
1
+ import React, { memo, ReactNode, useMemo } from "react";
2
+ import { RowProvider } from "../../core/RowContext";
3
+ import { render } from "../../lib/utils/ExprUtil";
4
+ import { useTableContext } from "./TableContext";
5
+
6
+ // import "./DataTable.css";
7
+
8
+ // ============================================================================
9
+ // TYPE DEFINITIONS (unchanged from original)
10
+ // ============================================================================
11
+
12
+ export interface ColumnDefinition {
13
+ id?: string;
14
+ title?: string | React.ReactNode;
15
+ width?: string | number;
16
+ align?: "left" | "center" | "right";
17
+ render?: (value: any, row: any, rowIndex: number) => React.ReactNode;
18
+ formatter?: (value: any, row?: any) => string;
19
+ colspan?: number;
20
+ rowspan?: number;
21
+ headerRow?: number;
22
+ datatype?: "string" | "number" | "date" | "boolean" | "currency" | "expr";
23
+ subrow?: number;
24
+ visible?: boolean;
25
+ sortable?: boolean;
26
+ expr?: string;
27
+ primary?: boolean;
28
+ component?: string | null;
29
+ }
30
+
31
+ export interface DataTableProps {
32
+ // When used INSIDE TableProvider these can be omitted; the context drives them.
33
+ data?: any[];
34
+ columns?: ColumnDefinition[];
35
+
36
+ loading?: boolean;
37
+ emptyMessage?: string;
38
+ striped?: boolean;
39
+ bordered?: boolean;
40
+ hover?: boolean;
41
+ dense?: boolean;
42
+ stickyHeader?: boolean;
43
+ maxHeight?: string | number;
44
+ rowKey?: string;
45
+ onRowClick?: (row: any, rowIndex: number) => void;
46
+ onCellClick?: (value: any, row: any, column: ColumnDefinition, rowIndex: number, colIndex: number) => void;
47
+ selectedRows?: string[];
48
+ onSelectionChange?: (selectedKeys: string[]) => void;
49
+ selectable?: boolean;
50
+ className?: string;
51
+ rowClassName?: (row: any, rowIndex: number) => string;
52
+ cellRenderer?: (value: any, row: any, column: ColumnDefinition) => React.ReactNode;
53
+ headerRenderer?: (column: ColumnDefinition) => React.ReactNode;
54
+ multiRowHeader?: ColumnDefinition[][];
55
+ rowActions?: (row: any, rowIndex: number) => React.ReactNode;
56
+ rowsPerItem?: number;
57
+ }
58
+
59
+ interface SortConfig {
60
+ key: string | null;
61
+ direction: "asc" | "desc" | null;
62
+ }
63
+
64
+ // ============================================================================
65
+ // HEADER sub-component – pure, only re-renders when columns change
66
+ // ============================================================================
67
+
68
+ interface TableHeaderProps {
69
+ visibleColumns: ColumnDefinition[];
70
+ selectable: boolean;
71
+ rowActions?: (row: any, rowIndex: number) => React.ReactNode;
72
+ data: any[];
73
+ selectedRows: string[];
74
+ handleSelectAll: (e: React.ChangeEvent<HTMLInputElement>) => void;
75
+ sortConfig: SortConfig;
76
+ handleSort: (columnId: string, sortable?: boolean) => void;
77
+ headerRenderer?: (column: ColumnDefinition) => React.ReactNode;
78
+ multiRowHeader?: ColumnDefinition[][];
79
+ }
80
+
81
+ const TableHeader = memo(
82
+ ({
83
+ visibleColumns,
84
+ selectable,
85
+ rowActions,
86
+ data,
87
+ selectedRows,
88
+ handleSelectAll,
89
+ sortConfig,
90
+ handleSort,
91
+ headerRenderer,
92
+ multiRowHeader,
93
+ }: TableHeaderProps) => {
94
+ const getColumnKey = (column: ColumnDefinition, index: number): string => column.id || column.expr || `col-${index}`;
95
+
96
+ const getColumnTitle = (column: ColumnDefinition): React.ReactNode => column.title || column.id || "";
97
+
98
+ const renderHeader = (column: ColumnDefinition) => {
99
+ if (headerRenderer) return headerRenderer(column);
100
+ const title = getColumnTitle(column);
101
+ if (!title) return null;
102
+ return (
103
+ <div
104
+ style={{
105
+ display: "flex",
106
+ alignItems: "center",
107
+ justifyContent: column.align === "center" ? "center" : column.align === "right" ? "flex-end" : "flex-start",
108
+ gap: "6px",
109
+ cursor: column.sortable ? "pointer" : "default",
110
+ userSelect: "none",
111
+ }}
112
+ onClick={() => column.id && handleSort(column.id, column.sortable)}
113
+ >
114
+ {title}
115
+ {column.sortable && column.id && sortConfig.key === column.id && (
116
+ <span style={{ fontSize: "10px", color: "#3b82f6" }}>{sortConfig.direction === "asc" ? "▲" : "▼"}</span>
117
+ )}
118
+ </div>
119
+ );
120
+ };
121
+
122
+ if (multiRowHeader && multiRowHeader.length > 0) {
123
+ return (
124
+ <>
125
+ {multiRowHeader.map((headerRow, rowIndex) => (
126
+ <tr key={`header-row-${rowIndex}`}>
127
+ {selectable && rowIndex === 0 && (
128
+ <th className="dt-checkbox-cell" rowSpan={multiRowHeader.length}>
129
+ <input type="checkbox" onChange={handleSelectAll} checked={data.length > 0 && selectedRows.length === data.length} />
130
+ </th>
131
+ )}
132
+ {headerRow.map((column, colIndex) => (
133
+ <th
134
+ key={getColumnKey(column, colIndex)}
135
+ style={{ width: column.width, textAlign: column.align || "left" }}
136
+ colSpan={column.colspan}
137
+ rowSpan={column.rowspan}
138
+ >
139
+ {renderHeader(column)}
140
+ </th>
141
+ ))}
142
+ {rowActions && rowIndex === 0 && (
143
+ <th className="dt-actions-cell" rowSpan={multiRowHeader.length}>
144
+ Actions
145
+ </th>
146
+ )}
147
+ </tr>
148
+ ))}
149
+ </>
150
+ );
151
+ }
152
+
153
+ // Auto multi-row detection
154
+ const needsMultiRow = visibleColumns.some(
155
+ (col) => (col.rowspan && col.rowspan > 1) || (col.colspan && col.colspan > 1) || (col.headerRow !== undefined && col.headerRow > 0),
156
+ );
157
+
158
+ if (needsMultiRow) {
159
+ const maxHeaderRow = Math.max(...visibleColumns.filter((col) => !col.subrow || col.subrow === 0).map((col) => col.headerRow ?? 0), 0);
160
+ const totalHeaderRows = maxHeaderRow + 1;
161
+ const headerRows: JSX.Element[] = [];
162
+
163
+ for (let rowIdx = 0; rowIdx <= maxHeaderRow; rowIdx++) {
164
+ const colsInRow = visibleColumns.filter((col) => {
165
+ if (col.subrow && col.subrow > 0) return false;
166
+ return (col.headerRow ?? 0) === rowIdx;
167
+ });
168
+ if (colsInRow.length === 0) continue;
169
+
170
+ headerRows.push(
171
+ <tr key={`header-row-${rowIdx}`}>
172
+ {selectable && rowIdx === 0 && (
173
+ <th className="dt-checkbox-cell" rowSpan={totalHeaderRows}>
174
+ <input type="checkbox" onChange={handleSelectAll} checked={data.length > 0 && selectedRows.length === data.length} />
175
+ </th>
176
+ )}
177
+ {colsInRow.map((column, index) => (
178
+ <th
179
+ key={getColumnKey(column, index)}
180
+ style={{
181
+ width: column.width,
182
+ textAlign: column.align || (column.colspan ? "center" : "left"),
183
+ }}
184
+ rowSpan={column.rowspan || 1}
185
+ colSpan={column.colspan || 1}
186
+ className={column.sortable ? "sortable" : ""}
187
+ >
188
+ {renderHeader(column)}
189
+ </th>
190
+ ))}
191
+ {rowActions && rowIdx === 0 && (
192
+ <th className="dt-actions-cell" rowSpan={totalHeaderRows}>
193
+ Actions
194
+ </th>
195
+ )}
196
+ </tr>,
197
+ );
198
+ }
199
+ return <>{headerRows}</>;
200
+ }
201
+
202
+ // Simple single-row header
203
+ return (
204
+ <tr>
205
+ {selectable && (
206
+ <th className="dt-checkbox-cell">
207
+ <input type="checkbox" onChange={handleSelectAll} checked={data.length > 0 && selectedRows.length === data.length} />
208
+ </th>
209
+ )}
210
+ {visibleColumns.map((column, index) => {
211
+ if (column.subrow && column.subrow > 0) return null;
212
+ return (
213
+ <th
214
+ key={getColumnKey(column, index)}
215
+ style={{ width: column.width, textAlign: column.align || "left" }}
216
+ className={column.sortable ? "sortable" : ""}
217
+ >
218
+ {renderHeader(column)}
219
+ </th>
220
+ );
221
+ })}
222
+ {rowActions && <th className="dt-actions-cell">Actions</th>}
223
+ </tr>
224
+ );
225
+ },
226
+ );
227
+
228
+ // ============================================================================
229
+ // BODY sub-component – pure, only re-renders when rows change
230
+ // ============================================================================
231
+
232
+ interface TableBodyProps {
233
+ sortedData: any[];
234
+ visibleColumns: ColumnDefinition[];
235
+ emptyMessage: string;
236
+ selectable: boolean;
237
+ rowActions?: (row: any, rowIndex: number) => React.ReactNode;
238
+ selectedRows: string[];
239
+ handleSelectRow: (row: any) => void;
240
+ handleRowClick: (row: any, rowIndex: number) => void;
241
+ handleCellClick: (e: React.MouseEvent, value: any, row: any, column: ColumnDefinition, rowIndex: number, colIndex: number) => void;
242
+ rowClassName?: (row: any, rowIndex: number) => string;
243
+ rowKey: string;
244
+ rowsPerItem: number;
245
+ cellRenderer?: (value: any, row: any, column: ColumnDefinition) => React.ReactNode;
246
+ }
247
+
248
+ const TableBody = memo(
249
+ ({
250
+ sortedData,
251
+ visibleColumns,
252
+ emptyMessage,
253
+ selectable,
254
+ rowActions,
255
+ selectedRows,
256
+ handleSelectRow,
257
+ handleRowClick,
258
+ handleCellClick,
259
+ rowClassName,
260
+ rowKey,
261
+ rowsPerItem,
262
+ cellRenderer,
263
+ }: TableBodyProps) => {
264
+ const generateKey = () => `RP-${Math.random().toString(36).slice(2)}`;
265
+ const getNestedValue = (obj: any, path?: string): any => {
266
+ if (!path) return null;
267
+ return path.split(".").reduce((acc, part) => acc && acc[part], obj);
268
+ };
269
+
270
+ const getRowKey = (row: any): string => row[rowKey] || JSON.stringify(row);
271
+ const isRowSelected = (row: any): boolean => selectedRows.includes(getRowKey(row));
272
+ const getColumnKey = (column: ColumnDefinition, index: number): string => column.id || column.expr || `col-${index}`;
273
+
274
+ /*
275
+ const renderExpression = (expr: string, row: any): string => {
276
+ try {
277
+ return expr.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (_match, path) => {
278
+ const value = getNestedValue(row, path);
279
+ return value != null ? String(value) : "";
280
+ });
281
+ } catch (err: any) {
282
+ return `Error: ${err.message}`;
283
+ }
284
+ };
285
+ */
286
+
287
+ const formatValue = (value: any, datatype = "string", row: any): string => {
288
+ if (value === null || value === undefined) return "";
289
+ switch (datatype) {
290
+ case "number":
291
+ return typeof value === "number" ? value.toLocaleString() : value;
292
+ case "currency":
293
+ return typeof value === "number" ? `$${value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : value;
294
+ case "date":
295
+ return value instanceof Date ? value.toLocaleDateString() : new Date(value).toLocaleDateString();
296
+ case "boolean":
297
+ return value ? "✓" : "✗";
298
+ default:
299
+ return String(value);
300
+ }
301
+ };
302
+
303
+ const renderCellContent = (row: any, column: ColumnDefinition, rowIndex: number) => {
304
+ if (column.expr) {
305
+ const rendered = render(column.expr, row);
306
+ if (column.render) return column.render(rendered, row, rowIndex);
307
+ return <span dangerouslySetInnerHTML={{ __html: rendered }} />;
308
+ }
309
+ const value = column.id ? getNestedValue(row, column.id) : null;
310
+ if (cellRenderer) return cellRenderer(value, row, column);
311
+ if (column.render) return column.render(value, row, rowIndex);
312
+ if (column.formatter) return column.formatter(value, row);
313
+ return formatValue(value, column.datatype || "string", row);
314
+ };
315
+
316
+ if (!sortedData || sortedData.length === 0) {
317
+ return (
318
+ <tr>
319
+ <td colSpan={visibleColumns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} style={{ textAlign: "center", padding: "24px" }}>
320
+ {emptyMessage}
321
+ </td>
322
+ </tr>
323
+ );
324
+ }
325
+
326
+ return (
327
+ <>
328
+ {sortedData.map((row, itemIndex) => {
329
+ const isSelected = isRowSelected(row);
330
+ const customRowClass = rowClassName ? rowClassName(row, itemIndex) : "";
331
+ const rows: ReactNode[] = [];
332
+
333
+ for (let subrowIndex = 0; subrowIndex < rowsPerItem; subrowIndex++) {
334
+ const rowClasses = [isSelected && "dt-selected", customRowClass].filter(Boolean).join(" ");
335
+
336
+ rows.push(
337
+ <RowProvider key={generateKey()} data={row}>
338
+ <tr key={`${getRowKey(row)}-subrow-${subrowIndex}`} className={rowClasses} onClick={() => handleRowClick(row, itemIndex)}>
339
+ {selectable && subrowIndex === 0 && (
340
+ <td className="dt-checkbox-cell" rowSpan={rowsPerItem}>
341
+ <input type="checkbox" checked={isSelected} onChange={() => handleSelectRow(row)} onClick={(e) => e.stopPropagation()} />
342
+ </td>
343
+ )}
344
+ {visibleColumns.map((column, colIndex) => {
345
+ if (!column.id && !column.expr) return null;
346
+ if ((column.subrow || 0) !== subrowIndex) return null;
347
+ if (column.subrow && column.subrow > 0 && subrowIndex > 0) return null;
348
+
349
+ const value = column.id ? getNestedValue(row, column.id) : null;
350
+ return (
351
+ <td
352
+ key={getColumnKey(column, colIndex)}
353
+ style={{ textAlign: column.align || "left" }}
354
+ className={`
355
+ ${column.datatype === "number" || column.datatype === "currency" ? "dt-numeric" : ""}
356
+ ${column.datatype === "boolean" ? "dt-boolean" : ""}
357
+ `.trim()}
358
+ onClick={(e) => handleCellClick(e, value, row, column, itemIndex, colIndex)}
359
+ >
360
+ {renderCellContent(row, column, itemIndex)}
361
+ </td>
362
+ );
363
+ })}
364
+ {rowActions && subrowIndex === 0 && (
365
+ <td className="dt-actions-cell" rowSpan={rowsPerItem}>
366
+ {rowActions(row, itemIndex)}
367
+ </td>
368
+ )}
369
+ </tr>
370
+ </RowProvider>,
371
+ );
372
+ }
373
+ return rows;
374
+ })}
375
+ </>
376
+ );
377
+ },
378
+ );
379
+
380
+ // ============================================================================
381
+ // DATATABLE COMPONENT
382
+ // ============================================================================
383
+
384
+ export const DataTable: React.FC<DataTableProps> = ({
385
+ // Direct props (fallback when used outside TableProvider)
386
+ data: propData,
387
+ columns: propColumns,
388
+
389
+ loading: propLoading = false,
390
+ emptyMessage = "No data available",
391
+ striped = false,
392
+ bordered = false,
393
+ hover = true,
394
+ dense = false,
395
+ stickyHeader = false,
396
+ maxHeight,
397
+ rowKey = "id",
398
+ onRowClick,
399
+ onCellClick,
400
+ selectedRows = [],
401
+ onSelectionChange,
402
+ selectable = false,
403
+ className = "",
404
+ rowClassName,
405
+ cellRenderer,
406
+ headerRenderer,
407
+ multiRowHeader,
408
+ rowActions,
409
+ rowsPerItem = 1,
410
+ }) => {
411
+ // Try to consume context; if not inside a provider, ctx will be null.
412
+ let ctxColumns: ColumnDefinition[] | undefined;
413
+ let ctxRows: any[] | undefined;
414
+ let ctxLoading: boolean | undefined;
415
+
416
+ try {
417
+ // eslint-disable-next-line react-hooks/rules-of-hooks
418
+ const ctx = useTableContext();
419
+ ctxColumns = ctx.columns;
420
+ ctxRows = ctx.rows;
421
+ ctxLoading = ctx.loading;
422
+ } catch {
423
+ // Not inside TableProvider – use props directly
424
+ }
425
+
426
+ // Context wins over direct props
427
+ const data: any[] = ctxRows ?? propData ?? [];
428
+ const columns: ColumnDefinition[] = propColumns ?? ctxColumns ?? [];
429
+ const loading = ctxLoading ?? propLoading;
430
+
431
+ // ---- Sort state ----
432
+ const [sortConfig, setSortConfig] = React.useState<SortConfig>({
433
+ key: null,
434
+ direction: null,
435
+ });
436
+
437
+ // ---- Derived values ----
438
+ const visibleColumns = useMemo(
439
+ () =>
440
+ columns.filter((col) => {
441
+ if (col.visible === false) return false;
442
+ return col.title || col.id || col.expr || col.render;
443
+ }),
444
+ [columns],
445
+ );
446
+
447
+ const sortedData = useMemo(() => {
448
+ if (!sortConfig.key) return data;
449
+ const getNestedValue = (obj: any, path: string): any => path.split(".").reduce((acc, part) => acc && acc[part], obj);
450
+
451
+ return [...data].sort((a, b) => {
452
+ const aVal = getNestedValue(a, sortConfig.key!);
453
+ const bVal = getNestedValue(b, sortConfig.key!);
454
+ if (aVal === bVal) return 0;
455
+ if (aVal == null) return 1;
456
+ if (bVal == null) return -1;
457
+ const cmp = aVal < bVal ? -1 : 1;
458
+ return sortConfig.direction === "asc" ? cmp : -cmp;
459
+ });
460
+ }, [data, sortConfig]);
461
+
462
+ // ---- Handlers ----
463
+ const handleRowClick = (row: any, rowIndex: number) => onRowClick?.(row, rowIndex);
464
+
465
+ const handleCellClick = (e: React.MouseEvent, value: any, row: any, column: ColumnDefinition, rowIndex: number, colIndex: number) => {
466
+ e.stopPropagation();
467
+ onCellClick?.(value, row, column, rowIndex, colIndex);
468
+ };
469
+
470
+ const getRowKey = (row: any): string => row[rowKey] || JSON.stringify(row);
471
+
472
+ const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
473
+ if (!onSelectionChange) return;
474
+ if (e.target.checked) {
475
+ onSelectionChange(data.map((row) => getRowKey(row)));
476
+ } else {
477
+ onSelectionChange([]);
478
+ }
479
+ };
480
+
481
+ const handleSelectRow = (row: any) => {
482
+ if (!onSelectionChange) return;
483
+ const key = getRowKey(row);
484
+ if (selectedRows.includes(key)) {
485
+ onSelectionChange(selectedRows.filter((k) => k !== key));
486
+ } else {
487
+ onSelectionChange([...selectedRows, key]);
488
+ }
489
+ };
490
+
491
+ const handleSort = (columnId: string, sortable?: boolean) => {
492
+ if (!sortable) return;
493
+ setSortConfig((prev) => {
494
+ if (prev.key === columnId) {
495
+ if (prev.direction === "asc") return { key: columnId, direction: "desc" };
496
+ if (prev.direction === "desc") return { key: null, direction: null };
497
+ }
498
+ return { key: columnId, direction: "asc" };
499
+ });
500
+ };
501
+
502
+ // ---- CSS ----
503
+ const tableClasses = [
504
+ "data-table",
505
+ striped && "dt-striped",
506
+ bordered && "dt-bordered",
507
+ hover && "dt-hover",
508
+ dense && "dt-dense",
509
+ stickyHeader && "dt-sticky-header",
510
+ className,
511
+ ]
512
+ .filter(Boolean)
513
+ .join(" ");
514
+
515
+ const containerStyle: React.CSSProperties = {};
516
+ if (maxHeight) {
517
+ containerStyle.maxHeight = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
518
+ containerStyle.overflowY = "auto";
519
+ }
520
+
521
+ // ---- Render ----
522
+ if (loading) {
523
+ return (
524
+ <div className="dt-loading">
525
+ <div className="dt-spinner"></div>
526
+ <p>Loading...</p>
527
+ </div>
528
+ );
529
+ }
530
+
531
+ return (
532
+ <div className="dt-container" style={containerStyle}>
533
+ <table className={tableClasses}>
534
+ <thead>
535
+ {/* TableHeader only re-renders when columns or sort config changes */}
536
+ <TableHeader
537
+ visibleColumns={visibleColumns}
538
+ selectable={selectable}
539
+ rowActions={rowActions}
540
+ data={data}
541
+ selectedRows={selectedRows}
542
+ handleSelectAll={handleSelectAll}
543
+ sortConfig={sortConfig}
544
+ handleSort={handleSort}
545
+ headerRenderer={headerRenderer}
546
+ multiRowHeader={multiRowHeader}
547
+ />
548
+ </thead>
549
+ <tbody>
550
+ {/* TableBody only re-renders when rows change */}
551
+ <TableBody
552
+ sortedData={sortedData}
553
+ visibleColumns={visibleColumns}
554
+ emptyMessage={emptyMessage}
555
+ selectable={selectable}
556
+ rowActions={rowActions}
557
+ selectedRows={selectedRows}
558
+ handleSelectRow={handleSelectRow}
559
+ handleRowClick={handleRowClick}
560
+ handleCellClick={handleCellClick}
561
+ rowClassName={rowClassName}
562
+ rowKey={rowKey}
563
+ rowsPerItem={rowsPerItem}
564
+ cellRenderer={cellRenderer}
565
+ />
566
+ </tbody>
567
+ </table>
568
+ </div>
569
+ );
570
+ };
571
+
572
+ export default DataTable;
@@ -0,0 +1,42 @@
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
+ export interface ListHandlerConfig {
6
+ tenant: string;
7
+ module: string;
8
+ api: string;
9
+ params?: Record<string, any>;
10
+ cols: string[];
11
+ rowsPerPage?: number;
12
+ disableTotalCount?: boolean;
13
+ useCursorPagination?: boolean;
14
+ }
15
+ export type ListActionHandler = {
16
+ getData: () => any[];
17
+ load: () => Promise<void>;
18
+ reset: () => Promise<void>;
19
+ moveNextPage: () => Promise<void>;
20
+ movePrevPage: () => Promise<void>;
21
+ moveFirstPage: () => Promise<void>;
22
+ moveLastPage: () => Promise<void>;
23
+ moveToPage: (page: number) => Promise<void>;
24
+ setFilter: (filter: Record<string, any>) => Promise<void>;
25
+ resetFilter: () => Promise<void>;
26
+ doSearch: (text: string) => Promise<void>;
27
+ sortAsc: (column: string) => Promise<void>;
28
+ sortDesc: (column: string) => Promise<void>;
29
+ setRowsPerPage: (rows: number) => Promise<void>;
30
+ hasNextPage: () => boolean;
31
+ hasPrevPage: () => boolean;
32
+ getTotalPageCount: () => number;
33
+ getTotalRecordCount: () => number;
34
+ getCurrentPage: () => number;
35
+ getCurrentSortedColumn: () => string | null;
36
+ getCurrentSortDirection: () => "asc" | "desc" | null;
37
+ getRowsPerPage: () => number;
38
+ getSearchText: () => string;
39
+ getCustomFilter: () => Record<string, any>;
40
+ };
41
+ export declare const ListHandler: (config: ListHandlerConfig) => ListActionHandler;
42
+ export default ListHandler;