@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,782 @@
1
+ import { Columns, Eye, RefreshCcw, Search, Trash } from "lucide-react";
2
+ import React, { useCallback, useState } from "react";
3
+ import { useApp } from "../../core/AppContext";
4
+ import { DynamicComponent } from "../../core/DynamicComponent";
5
+ import { usePageContext } from "../../core/PageContext";
6
+ import Panel from "../../core/Panel";
7
+ import useDependHandler from "../../core/UIDependHandler";
8
+ import { replaceValues } from "../../lib/utils/BeanUtils";
9
+ import { getUrlPageParams } from "../../lib/utils/PageUtils";
10
+ import { ColumnDefinition, DataTable } from "./DataTable";
11
+ import ListHandler, { ListActionHandler, ListHandlerConfig } from "./ListHandler";
12
+ import { DataConfig, TableProvider, useTableContext } from "./TableContext";
13
+ // import "./DataList.css";
14
+
15
+ // ============================================================================
16
+ // TYPE DEFINITIONS
17
+ // ============================================================================
18
+
19
+ export interface FilterDefinition {
20
+ name: string;
21
+ component: string;
22
+ attr?: Record<string, any>;
23
+ }
24
+
25
+ export interface ActionDefinition {
26
+ label?: string;
27
+ icon?: string | React.ReactNode;
28
+ component: string;
29
+ attr: Record<string, any>;
30
+ variant?: "default" | "primary" | "danger";
31
+ show?: (row: any) => boolean;
32
+ }
33
+
34
+ export interface BulkActionDefinition {
35
+ label: string;
36
+ icon?: string;
37
+ onClick: (selectedRows: any[]) => void;
38
+ variant?: "default" | "primary" | "danger";
39
+ }
40
+
41
+ export interface ToolbarActionDefinition {
42
+ label: string;
43
+ icon?: string;
44
+ component: string;
45
+ attr: Record<string, any>;
46
+ variant?: "default" | "primary";
47
+ }
48
+
49
+ // ============================================================================
50
+ // ATTR — the only prop DataList accepts
51
+ // ============================================================================
52
+
53
+ export interface DataListAttr {
54
+ // Required
55
+ cols: ColumnDefinition[];
56
+ data: DataConfig;
57
+
58
+ // Pagination / fetch config
59
+ rowsPerPage?: number;
60
+ disableTotalCount?: boolean;
61
+
62
+ // Display
63
+ title?: string;
64
+ emptyMessage?: string;
65
+ errorMessage?: string;
66
+ striped?: boolean;
67
+ bordered?: boolean;
68
+ hover?: boolean;
69
+ dense?: boolean;
70
+
71
+ showPagination?: boolean;
72
+ paginationPosition?: "top" | "bottom" | "both";
73
+ showPageInfo?: boolean;
74
+ showTotalCount?: boolean;
75
+ showRowsPerPage?: boolean;
76
+ rowsPerPageOptions?: number[];
77
+
78
+ // Search
79
+ searchable?: boolean;
80
+ searchPlaceholder?: string;
81
+ searchDebounce?: number;
82
+ onSearchChange?: (text: string) => void;
83
+
84
+ // Filters
85
+ filters?: FilterDefinition[] | FilterDefinition[][];
86
+ showFilterPanel?: boolean;
87
+ onFilterChange?: (filters: Record<string, any>) => void;
88
+
89
+ // Sorting
90
+ sortable?: boolean;
91
+ defaultSort?: { column: string; direction: "asc" | "desc" };
92
+ showSortIndicator?: boolean;
93
+
94
+ // Selection
95
+ selectable?: boolean;
96
+ selectionMode?: "single" | "multiple";
97
+ onSelectionChange?: (selectedRows: any[]) => void;
98
+ selectOnRowClick?: boolean;
99
+
100
+ // Row / action callbacks
101
+ onRowClick?: (row: any, rowIndex: number) => void;
102
+ onRowDoubleClick?: (row: any, rowIndex: number) => void;
103
+ rowActions?: ActionDefinition[];
104
+ bulkActions?: BulkActionDefinition[];
105
+ showBulkActions?: boolean;
106
+ toolbarActions?: ToolbarActionDefinition[];
107
+ commonActions?: Record<string, any>;
108
+
109
+ // Toolbar
110
+ showToolbar?: boolean;
111
+ toolbarTitle?: string;
112
+ showRefreshButton?: boolean;
113
+ showExportButton?: boolean;
114
+
115
+ // Lifecycle callbacks
116
+ onLoad?: () => void;
117
+ onError?: (error: any) => void;
118
+ onRefresh?: () => void;
119
+ onExport?: () => void;
120
+
121
+ // Styling
122
+ className?: string;
123
+ rowClassName?: (row: any, rowIndex: number) => string;
124
+
125
+ depends?: string;
126
+ }
127
+
128
+ // ============================================================================
129
+ // PUBLIC DATALIST PROPS — only attr, nothing else
130
+ // ============================================================================
131
+
132
+ export interface DataListProps {
133
+ attr: DataListAttr;
134
+ }
135
+
136
+ // ============================================================================
137
+ // INNER DATALIST — lives inside TableProvider, consumes context
138
+ // ============================================================================
139
+
140
+ type InnerProps = Omit<DataListAttr, "data" | "rowsPerPage" | "disableTotalCount">;
141
+
142
+ const InnerDataList: React.FC<InnerProps> = ({
143
+ cols,
144
+ emptyMessage = "No data available",
145
+ errorMessage,
146
+ striped = false,
147
+ bordered = false,
148
+ hover = true,
149
+ dense = false,
150
+
151
+ showPagination = true,
152
+ paginationPosition = "bottom",
153
+ showPageInfo = true,
154
+ showTotalCount = true,
155
+ showRowsPerPage = true,
156
+ rowsPerPageOptions = [5, 10, 20, 50, 100],
157
+
158
+ searchable = false,
159
+ searchPlaceholder = "Search...",
160
+ searchDebounce = 300,
161
+ onSearchChange,
162
+
163
+ filters = [],
164
+ showFilterPanel = false,
165
+ onFilterChange,
166
+
167
+ sortable = true,
168
+ showSortIndicator = true,
169
+
170
+ selectable = false,
171
+ selectionMode = "multiple",
172
+ onSelectionChange,
173
+ selectOnRowClick = false,
174
+
175
+ onRowClick,
176
+ rowActions = [],
177
+ bulkActions = [],
178
+ showBulkActions = true,
179
+ toolbarActions = [],
180
+
181
+ showToolbar = true,
182
+ toolbarTitle,
183
+ showRefreshButton = true,
184
+ showExportButton = false,
185
+
186
+ onLoad,
187
+ onError,
188
+ onRefresh,
189
+ onExport,
190
+
191
+ className = "",
192
+ rowClassName,
193
+ depends,
194
+ }) => {
195
+ const { listHandler, columns, rows, setRows, loading: ctxLoading, setLoading } = useTableContext();
196
+
197
+ // ============================================================================
198
+ // STATE
199
+ // ============================================================================
200
+
201
+ const [internalLoading, setInternalLoading] = useState(false);
202
+ const [error, setError] = useState<string | null>(null);
203
+ const [selectedRows, setSelectedRows] = useState<string[]>([]);
204
+ const [searchText, setSearchText] = useState("");
205
+ const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
206
+ const [activeFilters, setActiveFilters] = useState<Record<string, any>>({});
207
+ const [showColumnToggle, setShowColumnToggle] = useState(false);
208
+ const [hiddenColumns, setHiddenColumns] = useState<string[]>([]);
209
+
210
+ const isLoading = ctxLoading || internalLoading;
211
+
212
+ const pageContext = usePageContext();
213
+
214
+ const refreshHandler = async () => {
215
+ // console.log("refresh via notify depends", pageContext?.uuid);
216
+ await handleRefresh();
217
+ };
218
+
219
+ useDependHandler({
220
+ name: depends ?? "datalist",
221
+ onRefresh: refreshHandler,
222
+ });
223
+
224
+ // ============================================================================
225
+ // LOAD DATA
226
+ // ============================================================================
227
+
228
+ const loadData = useCallback(async () => {
229
+ if (!listHandler) return;
230
+ setInternalLoading(true);
231
+ setLoading(true);
232
+ setError(null);
233
+ try {
234
+ await listHandler.load();
235
+ setRows([...listHandler.getData()]);
236
+ onLoad?.();
237
+ } catch (err: any) {
238
+ setError(err?.message || "Failed to load data");
239
+ onError?.(err);
240
+ } finally {
241
+ setInternalLoading(false);
242
+ setLoading(false);
243
+ }
244
+ }, [listHandler, onLoad, onError, setRows, setLoading]);
245
+
246
+ // NOTE: TableProvider already fires the initial load — no double-fetch here.
247
+
248
+ // ============================================================================
249
+ // SEARCH
250
+ // ============================================================================
251
+
252
+ const handleSearch = (text: string) => {
253
+ setSearchText(text);
254
+ if (searchTimeout) clearTimeout(searchTimeout);
255
+ const timeout = setTimeout(async () => {
256
+ if (!listHandler) return;
257
+ await listHandler.doSearch(text);
258
+ await loadData();
259
+ onSearchChange?.(text);
260
+ }, searchDebounce);
261
+ setSearchTimeout(timeout);
262
+ };
263
+
264
+ // ============================================================================
265
+ // FILTER
266
+ // ============================================================================
267
+
268
+ const handleFilterChange = async (field: string, value: any) => {
269
+ if (!listHandler) return;
270
+ const newFilters = { ...activeFilters, [field]: value };
271
+ setActiveFilters(newFilters);
272
+ await listHandler.setFilter(newFilters);
273
+ await loadData();
274
+ onFilterChange?.(newFilters);
275
+ };
276
+
277
+ const handleResetFilters = async () => {
278
+ if (!listHandler) return;
279
+ setActiveFilters({});
280
+ await listHandler.resetFilter();
281
+ await loadData();
282
+ onFilterChange?.({});
283
+ };
284
+
285
+ // ============================================================================
286
+ // SORT
287
+ // ============================================================================
288
+
289
+ const handleSort = async (columnId: string) => {
290
+ if (!sortable || !listHandler) return;
291
+ const currentSort = listHandler.getCurrentSortedColumn();
292
+ const currentDirection = listHandler.getCurrentSortDirection();
293
+ if (currentSort === columnId && currentDirection === "asc") {
294
+ await listHandler.sortDesc(columnId);
295
+ } else {
296
+ await listHandler.sortAsc(columnId);
297
+ }
298
+ await loadData();
299
+ };
300
+
301
+ // ============================================================================
302
+ // PAGINATION
303
+ // ============================================================================
304
+
305
+ const handlePageChange = async (page: number) => {
306
+ if (!listHandler) return;
307
+ await listHandler.moveToPage(page);
308
+ await loadData();
309
+ };
310
+ const handleNextPage = async () => {
311
+ if (!listHandler) return;
312
+ await listHandler.moveNextPage();
313
+ await loadData();
314
+ };
315
+ const handlePrevPage = async () => {
316
+ if (!listHandler) return;
317
+ await listHandler.movePrevPage();
318
+ await loadData();
319
+ };
320
+ const handleFirstPage = async () => {
321
+ if (!listHandler) return;
322
+ await listHandler.moveFirstPage();
323
+ await loadData();
324
+ };
325
+ const handleLastPage = async () => {
326
+ if (!listHandler) return;
327
+ await listHandler.moveLastPage();
328
+ await loadData();
329
+ };
330
+ const handleRowsPerPage = async (n: number) => {
331
+ if (!listHandler) return;
332
+ await listHandler.setRowsPerPage(n);
333
+ await loadData();
334
+ };
335
+
336
+ // ============================================================================
337
+ // SELECTION
338
+ // ============================================================================
339
+
340
+ const handleSelectionChange = (keys: string[]) => {
341
+ setSelectedRows(keys);
342
+ if (onSelectionChange) {
343
+ const selectedData = rows.filter((row) => keys.includes(row.id || JSON.stringify(row)));
344
+ onSelectionChange(selectedData);
345
+ }
346
+ };
347
+
348
+ // ============================================================================
349
+ // ROW CLICK
350
+ // ============================================================================
351
+
352
+ const handleRowClick = (row: any, rowIndex: number) => {
353
+ if (selectOnRowClick && selectable) {
354
+ const key = row.id || JSON.stringify(row);
355
+ if (selectionMode === "single") {
356
+ handleSelectionChange([key]);
357
+ } else {
358
+ const newSelection = selectedRows.includes(key) ? selectedRows.filter((k) => k !== key) : [...selectedRows, key];
359
+ handleSelectionChange(newSelection);
360
+ }
361
+ }
362
+ onRowClick?.(row, rowIndex);
363
+ };
364
+
365
+ // ============================================================================
366
+ // REFRESH
367
+ // ============================================================================
368
+
369
+ const handleRefresh = async () => {
370
+ await loadData();
371
+ onRefresh?.();
372
+ };
373
+
374
+ // ============================================================================
375
+ // COLUMN VISIBILITY
376
+ // ============================================================================
377
+
378
+ const visibleUserColumns = columns.filter((col) => {
379
+ const colId = col.id || String(col.title);
380
+ return !hiddenColumns.includes(colId);
381
+ });
382
+
383
+ // ============================================================================
384
+ // DISPLAY COLUMNS — actions + sort indicators
385
+ // ============================================================================
386
+
387
+ const renderActionCell = (row: any, rowIndex: number) => {
388
+ const visible = rowActions.filter((a) => !a.show || a.show(row));
389
+ if (visible.length === 0) return null;
390
+ const uuid = Math.random().toString(36).slice(2);
391
+ return (
392
+ <div className="dl-actions flex justify-center items-center">
393
+ {visible.map((action, i) => (
394
+ <div key={`${uuid}-${i}`}>
395
+ <DynamicComponent
396
+ config={{
397
+ component: action.component,
398
+ attr: { ...action.attr, opt: { data: row } },
399
+ }}
400
+ />
401
+ </div>
402
+ ))}
403
+ </div>
404
+ );
405
+ };
406
+
407
+ const displayColumns: ColumnDefinition[] =
408
+ rowActions.length > 0 && visibleUserColumns.length > 0
409
+ ? [
410
+ ...visibleUserColumns,
411
+ {
412
+ id: "_actions",
413
+ title: <div className="text-center w-full">Actions</div>,
414
+ align: "center",
415
+ render: (_, row, rowIndex) => renderActionCell(row, rowIndex),
416
+ },
417
+ ]
418
+ : visibleUserColumns;
419
+
420
+ const sortableColumns = displayColumns.map((col) => {
421
+ if (!sortable || !showSortIndicator || !listHandler) return col;
422
+ const currentSort = listHandler.getCurrentSortedColumn();
423
+ const currentDirection = listHandler.getCurrentSortDirection();
424
+ return {
425
+ ...col,
426
+ title: (
427
+ <div
428
+ className="dl-sortable-header"
429
+ onClick={() => col.id && col.id !== "_actions" && handleSort(col.id)}
430
+ style={{ cursor: col.id !== "_actions" ? "pointer" : "default" }}
431
+ >
432
+ {col.title}
433
+ {currentSort === col.id && <span className={`dl-sort-icon dl-sort-${currentDirection}`}>{currentDirection === "asc" ? "▲" : "▼"}</span>}
434
+ </div>
435
+ ),
436
+ };
437
+ });
438
+
439
+ // ============================================================================
440
+ // RENDER TOOLBAR
441
+ // ============================================================================
442
+
443
+ const renderToolbar = () => {
444
+ if (!showToolbar) return null;
445
+ const uuid = Math.random().toString(36).slice(2);
446
+ return (
447
+ <div className="dl-toolbar">
448
+ <div className="dl-toolbar-left">
449
+ {toolbarTitle && <h3 className="dl-toolbar-title">{toolbarTitle}</h3>}
450
+ <div className="dl-toolbar-right">
451
+ {showBulkActions &&
452
+ selectedRows.length > 0 &&
453
+ bulkActions.map((action, i) => (
454
+ <button
455
+ key={action.label || i}
456
+ className={`dl-btn dl-btn-${action.variant || "default"}`}
457
+ onClick={() => {
458
+ const selectedData = rows.filter((row) => selectedRows.includes(row.id || JSON.stringify(row)));
459
+ action.onClick(selectedData);
460
+ }}
461
+ >
462
+ {action.icon && <span>{action.icon}</span>}
463
+ {action.label}
464
+ </button>
465
+ ))}
466
+ {toolbarActions.map((action, i) => (
467
+ <div key={`${uuid}-${i}`}>
468
+ <DynamicComponent config={{ component: action.component, attr: action.attr || {} }} />
469
+ </div>
470
+ ))}
471
+ {showExportButton && (
472
+ <button className="dl-btn" onClick={onExport}>
473
+ <span>📥</span> Export
474
+ </button>
475
+ )}
476
+ </div>
477
+ </div>
478
+
479
+ <div className="flex items-center">
480
+ <RefreshButton show={showRefreshButton} onClick={handleRefresh} />
481
+
482
+ <ColumnToggle columns={columns} hiddenColumns={hiddenColumns} setHiddenColumns={setHiddenColumns} />
483
+
484
+ <SearchBox searchable={searchable} searchPlaceholder={searchPlaceholder} searchText={searchText} onSearch={handleSearch} />
485
+ </div>
486
+ </div>
487
+ );
488
+ };
489
+
490
+ // ============================================================================
491
+ // RENDER FILTER PANEL
492
+ // ============================================================================
493
+
494
+ const renderFilterPanel = () => {
495
+ if (!filters) return null;
496
+ return <Panel content={filters} />;
497
+ };
498
+
499
+ // ============================================================================
500
+ // RENDER PAGINATION
501
+ // ============================================================================
502
+
503
+ const renderPagination = () => {
504
+ if (!showPagination || !listHandler) return null;
505
+ const currentPage = listHandler.getCurrentPage();
506
+ const totalPages = listHandler.getTotalPageCount();
507
+ const totalRecords = listHandler.getTotalRecordCount();
508
+ const rowsPerPage = listHandler.getRowsPerPage();
509
+ const hasNext = listHandler.hasNextPage();
510
+ const hasPrev = listHandler.hasPrevPage();
511
+ const startRecord = (currentPage - 1) * rowsPerPage + 1;
512
+ const endRecord = Math.min(currentPage * rowsPerPage, totalRecords);
513
+
514
+ return (
515
+ <div className="dl-pagination">
516
+ <div className="dl-pagination-info">
517
+ {showPageInfo && showTotalCount && (
518
+ <span>
519
+ Showing {startRecord}-{endRecord} of {totalRecords} records
520
+ </span>
521
+ )}
522
+ {showPageInfo && !showTotalCount && <span>Page {currentPage}</span>}
523
+ </div>
524
+ <div className="dl-pagination-controls">
525
+ {showRowsPerPage && (
526
+ <div className="dl-rows-per-page">
527
+ <span>Rows per page:</span>
528
+ <select value={rowsPerPage} onChange={(e) => handleRowsPerPage(Number(e.target.value))}>
529
+ {rowsPerPageOptions.map((o) => (
530
+ <option key={o} value={o}>
531
+ {o}
532
+ </option>
533
+ ))}
534
+ </select>
535
+ </div>
536
+ )}
537
+ <button className="dl-page-btn" disabled={!hasPrev} onClick={handleFirstPage}>
538
+ First
539
+ </button>
540
+ <button className="dl-page-btn" disabled={!hasPrev} onClick={handlePrevPage}>
541
+ ← Prev
542
+ </button>
543
+ {totalPages > 0 &&
544
+ [...Array(Math.min(totalPages, 5))].map((_, i) => {
545
+ const page = i + 1;
546
+ return (
547
+ <button key={page} className={`dl-page-btn ${currentPage === page ? "active" : ""}`} onClick={() => handlePageChange(page)}>
548
+ {page}
549
+ </button>
550
+ );
551
+ })}
552
+ <button className="dl-page-btn" disabled={!hasNext} onClick={handleNextPage}>
553
+ Next →
554
+ </button>
555
+ <button className="dl-page-btn" disabled={!hasNext} onClick={handleLastPage}>
556
+ Last
557
+ </button>
558
+ </div>
559
+ </div>
560
+ );
561
+ };
562
+
563
+ // ============================================================================
564
+ // ERROR STATE
565
+ // ============================================================================
566
+
567
+ if (error && errorMessage) {
568
+ return (
569
+ <div className="dl-error">
570
+ <p>{errorMessage}</p>
571
+ <button onClick={handleRefresh}>Retry</button>
572
+ </div>
573
+ );
574
+ }
575
+
576
+ // ============================================================================
577
+ // MAIN RENDER
578
+ // ============================================================================
579
+
580
+ return (
581
+ <div className={`data-list ${className}`}>
582
+ {renderToolbar()}
583
+ {renderFilterPanel()}
584
+ {(paginationPosition === "both" || paginationPosition === "top") && renderPagination()}
585
+ <DataTable
586
+ data={rows}
587
+ columns={sortableColumns}
588
+ loading={isLoading}
589
+ emptyMessage={emptyMessage}
590
+ striped={striped}
591
+ bordered={bordered}
592
+ hover={hover}
593
+ dense={dense}
594
+ rowKey="id"
595
+ onRowClick={handleRowClick}
596
+ selectedRows={selectedRows}
597
+ onSelectionChange={handleSelectionChange}
598
+ selectable={selectable}
599
+ rowClassName={rowClassName}
600
+ />
601
+ {(paginationPosition === "both" || paginationPosition === "bottom") && rows.length > 0 && renderPagination()}
602
+ </div>
603
+ );
604
+ };
605
+
606
+ // ============================================================================
607
+ // PUBLIC DATALIST
608
+ // - Only accepts attr
609
+ // - All hooks (useApp, usePageContext) called here
610
+ // - factory defined here
611
+ // - resolves dynamic filter params before passing to TableProvider
612
+ // ============================================================================
613
+
614
+ export const DataList: React.FC<DataListProps> = ({ attr }) => {
615
+ const { cols, data, rowsPerPage, disableTotalCount, commonActions, rowActions, toolbarActions, ...rest } = attr;
616
+
617
+ // All hooks called here — this is a React component so hooks are valid
618
+ const { tenant, module } = useApp();
619
+ const pageContext = usePageContext();
620
+
621
+ // Factory defined here — ListHandler is a local import, no prop needed
622
+ const listHandlerFactory = (config: ListHandlerConfig): ListActionHandler => ListHandler(config);
623
+
624
+ // Resolve dynamic placeholders in filter before ListHandler is constructed.
625
+ // Merges pageContext.getAllData() (page state) and pageContext.getUrlParams()
626
+ // (URL/route params) as the source so both {pageParams.x} and {urlParams.x}
627
+ // style tokens are resolved.
628
+ const resolveParams = (): Record<string, any> | undefined => {
629
+ if (!data?.params) return undefined;
630
+ //clone the filter
631
+ const cloned = JSON.parse(JSON.stringify(data.params));
632
+ const { params } = getUrlPageParams();
633
+ return replaceValues(cloned, params);
634
+ };
635
+
636
+ const newRowActions = [...(rowActions || [])];
637
+ if (commonActions?.viewPage) {
638
+ newRowActions.unshift({
639
+ component: "ViewPage",
640
+ attr: {
641
+ ...commonActions.viewPage,
642
+ title: "View",
643
+ icon: <Eye size={20} />,
644
+ iconOnly: true,
645
+ },
646
+ });
647
+ }
648
+
649
+ if (commonActions?.deleteData) {
650
+ newRowActions.push({
651
+ component: "DeleteData",
652
+ attr: {
653
+ ...commonActions.deleteData,
654
+ title: "Delete",
655
+ icon: <Trash size={20} />,
656
+ iconOnly: true,
657
+ },
658
+ });
659
+ }
660
+
661
+ const newToolbarActions = [...(toolbarActions || [])];
662
+ if (commonActions?.newPage) {
663
+ newToolbarActions.unshift({
664
+ label: "New",
665
+ component: "ViewPage",
666
+ attr: {
667
+ ...commonActions.newPage,
668
+ title: "New",
669
+ },
670
+ });
671
+ }
672
+
673
+ if (commonActions?.filterPage) {
674
+ newToolbarActions.push({
675
+ label: "Filter",
676
+ component: "LookupPage",
677
+ attr: {
678
+ ...commonActions.filterPage,
679
+ title: "Filter",
680
+ },
681
+ });
682
+ }
683
+
684
+ return (
685
+ <TableProvider
686
+ data={data}
687
+ columns={cols}
688
+ rowsPerPage={rowsPerPage}
689
+ disableTotalCount={disableTotalCount}
690
+ listHandlerFactory={listHandlerFactory}
691
+ tenant={tenant ?? ""}
692
+ module={module ?? ""}
693
+ resolvedParams={resolveParams()}
694
+ >
695
+ <InnerDataList {...rest} cols={cols} rowActions={newRowActions} toolbarActions={newToolbarActions} />
696
+ </TableProvider>
697
+ );
698
+ };
699
+
700
+ export default DataList;
701
+
702
+ interface RefreshButtonProps {
703
+ show: boolean;
704
+ onClick: () => void;
705
+ size?: number;
706
+ }
707
+
708
+ const RefreshButton = ({ show, onClick, size = 18 }: RefreshButtonProps) => {
709
+ if (!show) return null;
710
+
711
+ return (
712
+ <span className="text-gray-600 hover:bg-gray-200 rounded p-2">
713
+ <RefreshCcw size={size} onClick={onClick} className="cursor-pointer" />
714
+ </span>
715
+ );
716
+ };
717
+
718
+ interface ColumnToggleProps {
719
+ columns: ColumnDefinition[];
720
+ hiddenColumns: string[];
721
+ setHiddenColumns: (fn: (prev: string[]) => string[]) => void;
722
+ }
723
+
724
+ const ColumnToggle = ({ columns, hiddenColumns, setHiddenColumns }: ColumnToggleProps) => {
725
+ const [showColumnToggle, setShowColumnToggle] = useState(false);
726
+
727
+ return (
728
+ <div className="relative">
729
+ <span
730
+ className="text-gray-600 hover:bg-gray-200 rounded p-2 cursor-pointer flex items-center"
731
+ onClick={() => setShowColumnToggle(!showColumnToggle)}
732
+ >
733
+ <Columns size={18} />
734
+ </span>
735
+ {showColumnToggle && (
736
+ <div className="absolute right-0 top-8 z-50 bg-white border border-gray-200 rounded-lg shadow-lg p-3 min-w-[180px]">
737
+ <p className="text-xs font-semibold text-gray-500 uppercase tracking-wide mb-2">Columns</p>
738
+ {columns.map((col) => {
739
+ const colId = col.id || String(col.title);
740
+ const isHidden = hiddenColumns.includes(colId);
741
+ return (
742
+ <label key={colId} className="flex items-center gap-2 py-1 cursor-pointer hover:bg-gray-50 px-1 rounded">
743
+ <input
744
+ type="checkbox"
745
+ checked={!isHidden}
746
+ onChange={() => setHiddenColumns((prev) => (isHidden ? prev.filter((id) => id !== colId) : [...prev, colId]))}
747
+ />
748
+ <span className="text-sm text-gray-700">{String(col.title || col.id)}</span>
749
+ </label>
750
+ );
751
+ })}
752
+ </div>
753
+ )}
754
+ </div>
755
+ );
756
+ };
757
+
758
+ interface SearchBoxProps {
759
+ searchable: boolean;
760
+ searchPlaceholder?: string;
761
+ searchText: string;
762
+ onSearch: (value: string) => void;
763
+ }
764
+
765
+ const SearchBox = ({ searchable, searchPlaceholder, searchText, onSearch }: SearchBoxProps) => {
766
+ if (!searchable) return null;
767
+
768
+ return (
769
+ <div className="dl-search-box ml-2">
770
+ <span>
771
+ <Search size={16} />
772
+ </span>
773
+ <input
774
+ type="text"
775
+ placeholder={searchPlaceholder}
776
+ value={searchText}
777
+ onChange={(e) => onSearch(e.target.value)}
778
+ className="focus:outline-none focus:ring-0 focus:shadow-none focus:bg-white"
779
+ />
780
+ </div>
781
+ );
782
+ };