nucleus-core-ts 0.8.6 → 0.8.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.
Files changed (209) hide show
  1. package/dist/client.js +1 -1
  2. package/dist/dist/index.d.ts +475 -12
  3. package/dist/fe/components/AbstractAnimatedBackground/index.js +437 -0
  4. package/dist/fe/components/AuthorizationPage/components/AuthorizationPage.js +841 -0
  5. package/dist/fe/components/AuthorizationPage/components/ClaimList.js +100 -0
  6. package/dist/fe/components/AuthorizationPage/components/RoleClaimEditor.js +232 -0
  7. package/dist/fe/components/AuthorizationPage/components/RoleList.js +115 -0
  8. package/dist/fe/components/AuthorizationPage/index.js +6 -0
  9. package/dist/fe/components/AuthorizationPage/store/index.js +117 -0
  10. package/dist/fe/components/AuthorizationPage/theme/index.js +137 -0
  11. package/dist/fe/components/AuthorizationPage/types/index.js +1 -0
  12. package/dist/fe/components/Button/components/Button.js +158 -0
  13. package/dist/fe/components/Button/components/ButtonSpinner.js +52 -0
  14. package/dist/fe/components/Button/index.js +3 -0
  15. package/dist/fe/components/Button/theme/index.js +186 -0
  16. package/dist/fe/components/Button/types/index.js +1 -0
  17. package/dist/fe/components/Button/utils/cn.js +5 -0
  18. package/dist/fe/components/Captcha/components/Captcha.js +311 -0
  19. package/dist/fe/components/Captcha/index.js +2 -0
  20. package/dist/fe/components/Captcha/theme.js +52 -0
  21. package/dist/fe/components/Captcha/types.js +1 -0
  22. package/dist/fe/components/ChangePasswordPage/components/ChangePasswordForm.js +242 -0
  23. package/dist/fe/components/ChangePasswordPage/components/ChangePasswordHeader.js +39 -0
  24. package/dist/fe/components/ChangePasswordPage/components/ChangePasswordPage.js +60 -0
  25. package/dist/fe/components/ChangePasswordPage/index.js +5 -0
  26. package/dist/fe/components/ChangePasswordPage/store/index.js +44 -0
  27. package/dist/fe/components/ChangePasswordPage/theme/index.js +87 -0
  28. package/dist/fe/components/ChangePasswordPage/types/index.js +1 -0
  29. package/dist/fe/components/Checkbox/components/Checkbox.js +115 -0
  30. package/dist/fe/components/Checkbox/components/CheckboxIcon.js +119 -0
  31. package/dist/fe/components/Checkbox/components/SwitchTrack.js +178 -0
  32. package/dist/fe/components/Checkbox/index.js +4 -0
  33. package/dist/fe/components/Checkbox/theme/index.js +221 -0
  34. package/dist/fe/components/Checkbox/types/index.js +1 -0
  35. package/dist/fe/components/Checkbox/utils/cn.js +5 -0
  36. package/dist/fe/components/DataTable/DataTable.js +225 -0
  37. package/dist/fe/components/DataTable/components/ActionCell.js +26 -0
  38. package/dist/fe/components/DataTable/components/DataCell.js +76 -0
  39. package/dist/fe/components/DataTable/components/EditableCell.js +221 -0
  40. package/dist/fe/components/DataTable/components/EmptyState.js +29 -0
  41. package/dist/fe/components/DataTable/components/HeaderCell.js +64 -0
  42. package/dist/fe/components/DataTable/components/InfiniteScrollTrigger.js +66 -0
  43. package/dist/fe/components/DataTable/components/LoadingSpinner.js +19 -0
  44. package/dist/fe/components/DataTable/components/ResizeHandle.js +41 -0
  45. package/dist/fe/components/DataTable/components/SelectionCell.js +105 -0
  46. package/dist/fe/components/DataTable/components/SkeletonRow.js +56 -0
  47. package/dist/fe/components/DataTable/components/SkeletonTable.js +83 -0
  48. package/dist/fe/components/DataTable/components/SortIcon.js +39 -0
  49. package/dist/fe/components/DataTable/components/TableHeader.js +49 -0
  50. package/dist/fe/components/DataTable/components/TableRow.js +118 -0
  51. package/dist/fe/components/DataTable/components/index.js +14 -0
  52. package/dist/fe/components/DataTable/hooks/index.js +2 -0
  53. package/dist/fe/components/DataTable/hooks/useAutoFitColumn.js +23 -0
  54. package/dist/fe/components/DataTable/hooks/useResizeColumn.js +122 -0
  55. package/dist/fe/components/DataTable/index.js +3 -0
  56. package/dist/fe/components/DataTable/store/index.js +97 -0
  57. package/dist/fe/components/DataTable/theme/index.js +144 -0
  58. package/dist/fe/components/DataTable/types/index.js +1 -0
  59. package/dist/fe/components/DataTable/utils/cn.js +5 -0
  60. package/dist/fe/components/DatePicker/components/CalendarGrid.js +95 -0
  61. package/dist/fe/components/DatePicker/components/CalendarHeader.js +152 -0
  62. package/dist/fe/components/DatePicker/components/DatePicker.js +381 -0
  63. package/dist/fe/components/DatePicker/components/MonthYearSelector.js +93 -0
  64. package/dist/fe/components/DatePicker/index.js +7 -0
  65. package/dist/fe/components/DatePicker/locales/index.js +1113 -0
  66. package/dist/fe/components/DatePicker/theme/index.js +315 -0
  67. package/dist/fe/components/DatePicker/types/index.js +1 -0
  68. package/dist/fe/components/DatePicker/utils/cn.js +5 -0
  69. package/dist/fe/components/DatePicker/utils/date.js +132 -0
  70. package/dist/fe/components/DevicesPage/components/DeviceCard.js +251 -0
  71. package/dist/fe/components/DevicesPage/components/DevicesHeader.js +42 -0
  72. package/dist/fe/components/DevicesPage/components/DevicesPage.js +450 -0
  73. package/dist/fe/components/DevicesPage/index.js +5 -0
  74. package/dist/fe/components/DevicesPage/store/index.js +55 -0
  75. package/dist/fe/components/DevicesPage/theme/index.js +131 -0
  76. package/dist/fe/components/DevicesPage/types/index.js +1 -0
  77. package/dist/fe/components/ForgotPasswordPage/components/ForgotPasswordForm.js +214 -0
  78. package/dist/fe/components/ForgotPasswordPage/components/ForgotPasswordHeader.js +42 -0
  79. package/dist/fe/components/ForgotPasswordPage/components/ForgotPasswordPage.js +59 -0
  80. package/dist/fe/components/ForgotPasswordPage/index.js +5 -0
  81. package/dist/fe/components/ForgotPasswordPage/store/index.js +28 -0
  82. package/dist/fe/components/ForgotPasswordPage/theme/index.js +87 -0
  83. package/dist/fe/components/ForgotPasswordPage/types/index.js +1 -0
  84. package/dist/fe/components/FormBuilder/components/FormBuilder.js +156 -0
  85. package/dist/fe/components/FormBuilder/components/FormField.js +218 -0
  86. package/dist/fe/components/FormBuilder/hooks/useFormBuilder.js +152 -0
  87. package/dist/fe/components/FormBuilder/index.js +4 -0
  88. package/dist/fe/components/FormBuilder/theme/index.js +134 -0
  89. package/dist/fe/components/FormBuilder/types/index.js +1 -0
  90. package/dist/fe/components/FormBuilder/utils/cn.js +5 -0
  91. package/dist/fe/components/FormBuilder/utils/fieldMapping.js +216 -0
  92. package/dist/fe/components/FormBuilder/utils/validation.js +78 -0
  93. package/dist/fe/components/LoginPage/components/LoginForm.js +214 -0
  94. package/dist/fe/components/LoginPage/components/LoginHeader.js +24 -0
  95. package/dist/fe/components/LoginPage/components/LoginPage.js +138 -0
  96. package/dist/fe/components/LoginPage/index.js +5 -0
  97. package/dist/fe/components/LoginPage/store/index.js +59 -0
  98. package/dist/fe/components/LoginPage/theme/index.js +98 -0
  99. package/dist/fe/components/LoginPage/types/index.js +1 -0
  100. package/dist/fe/components/MagicLinkVerifyPage/components/MagicLinkVerifyPage.js +200 -0
  101. package/dist/fe/components/MagicLinkVerifyPage/index.js +3 -0
  102. package/dist/fe/components/MagicLinkVerifyPage/store.js +20 -0
  103. package/dist/fe/components/MagicLinkVerifyPage/theme.js +36 -0
  104. package/dist/fe/components/MagicLinkVerifyPage/types.js +1 -0
  105. package/dist/fe/components/NucleusEntityShowcase.js +1409 -0
  106. package/dist/fe/components/NucleusTextInput/components/FloatingLabel.js +56 -0
  107. package/dist/fe/components/NucleusTextInput/components/InputIcons.js +258 -0
  108. package/dist/fe/components/NucleusTextInput/components/NucleusTextInput.js +321 -0
  109. package/dist/fe/components/NucleusTextInput/components/PasswordStrengthIndicator.js +104 -0
  110. package/dist/fe/components/NucleusTextInput/components/TypewriterText.js +56 -0
  111. package/dist/fe/components/NucleusTextInput/index.js +7 -0
  112. package/dist/fe/components/NucleusTextInput/theme/index.js +121 -0
  113. package/dist/fe/components/NucleusTextInput/types/index.js +1 -0
  114. package/dist/fe/components/NucleusTextInput/utils/cn.js +5 -0
  115. package/dist/fe/components/NucleusTextInput/utils/format.js +62 -0
  116. package/dist/fe/components/NucleusTextInput/utils/validation.js +191 -0
  117. package/dist/fe/components/ProfilePage/components/AddressCard.js +196 -0
  118. package/dist/fe/components/ProfilePage/components/PhoneCard.js +206 -0
  119. package/dist/fe/components/ProfilePage/components/ProfileHeader.js +150 -0
  120. package/dist/fe/components/ProfilePage/components/ProfilePage.js +1336 -0
  121. package/dist/fe/components/ProfilePage/index.js +6 -0
  122. package/dist/fe/components/ProfilePage/store/index.js +115 -0
  123. package/dist/fe/components/ProfilePage/theme/index.js +168 -0
  124. package/dist/fe/components/ProfilePage/types/index.js +1 -0
  125. package/dist/fe/components/RangePicker/components/RangePicker.js +338 -0
  126. package/dist/fe/components/RangePicker/components/RangeThumb.js +68 -0
  127. package/dist/fe/components/RangePicker/components/RangeTooltip.js +45 -0
  128. package/dist/fe/components/RangePicker/components/RangeTrack.js +32 -0
  129. package/dist/fe/components/RangePicker/index.js +5 -0
  130. package/dist/fe/components/RangePicker/theme/index.js +88 -0
  131. package/dist/fe/components/RangePicker/types/index.js +1 -0
  132. package/dist/fe/components/RangePicker/utils/cn.js +3 -0
  133. package/dist/fe/components/RegisterPage/components/PasswordStrengthIndicator.js +107 -0
  134. package/dist/fe/components/RegisterPage/components/RegisterForm.js +322 -0
  135. package/dist/fe/components/RegisterPage/components/RegisterHeader.js +23 -0
  136. package/dist/fe/components/RegisterPage/components/RegisterPage.js +85 -0
  137. package/dist/fe/components/RegisterPage/index.js +6 -0
  138. package/dist/fe/components/RegisterPage/store/index.js +106 -0
  139. package/dist/fe/components/RegisterPage/theme/index.js +128 -0
  140. package/dist/fe/components/RegisterPage/types/index.js +1 -0
  141. package/dist/fe/components/ResetPasswordPage/components/ResetPasswordForm.js +347 -0
  142. package/dist/fe/components/ResetPasswordPage/components/ResetPasswordHeader.js +42 -0
  143. package/dist/fe/components/ResetPasswordPage/components/ResetPasswordPage.js +61 -0
  144. package/dist/fe/components/ResetPasswordPage/index.js +5 -0
  145. package/dist/fe/components/ResetPasswordPage/store/index.js +36 -0
  146. package/dist/fe/components/ResetPasswordPage/theme/index.js +99 -0
  147. package/dist/fe/components/ResetPasswordPage/types/index.js +1 -0
  148. package/dist/fe/components/SearchBox/components/SearchBox.js +271 -0
  149. package/dist/fe/components/SearchBox/components/SearchBoxDropdown.js +87 -0
  150. package/dist/fe/components/SearchBox/index.js +5 -0
  151. package/dist/fe/components/SearchBox/theme/index.js +184 -0
  152. package/dist/fe/components/SearchBox/types/index.js +1 -0
  153. package/dist/fe/components/SearchBox/utils/cn.js +5 -0
  154. package/dist/fe/components/SearchBox/utils/debounce.js +22 -0
  155. package/dist/fe/components/SearchBox/utils/sanitize.js +48 -0
  156. package/dist/fe/components/SelectBox/components/SelectBox.js +364 -0
  157. package/dist/fe/components/SelectBox/components/SelectDropdown.js +92 -0
  158. package/dist/fe/components/SelectBox/components/SelectOptionItem.js +43 -0
  159. package/dist/fe/components/SelectBox/components/SelectTrigger.js +22 -0
  160. package/dist/fe/components/SelectBox/index.js +5 -0
  161. package/dist/fe/components/SelectBox/theme/index.js +98 -0
  162. package/dist/fe/components/SelectBox/types/index.js +1 -0
  163. package/dist/fe/components/SelectBox/utils/cn.js +3 -0
  164. package/dist/fe/components/SetPasswordPage/components/PasswordStrengthIndicator.js +107 -0
  165. package/dist/fe/components/SetPasswordPage/components/SetPasswordForm.js +142 -0
  166. package/dist/fe/components/SetPasswordPage/components/SetPasswordHeader.js +23 -0
  167. package/dist/fe/components/SetPasswordPage/components/SetPasswordPage.js +263 -0
  168. package/dist/fe/components/SetPasswordPage/index.js +7 -0
  169. package/dist/fe/components/SetPasswordPage/store/index.js +79 -0
  170. package/dist/fe/components/SetPasswordPage/theme/index.js +98 -0
  171. package/dist/fe/components/SetPasswordPage/types/index.js +12 -0
  172. package/dist/fe/components/UsersPage/components/InviteUserModal.js +262 -0
  173. package/dist/fe/components/UsersPage/components/Pagination.js +147 -0
  174. package/dist/fe/components/UsersPage/components/RoleAssignmentModal.js +186 -0
  175. package/dist/fe/components/UsersPage/components/StatsCards.js +124 -0
  176. package/dist/fe/components/UsersPage/components/UserDetailDrawer.js +444 -0
  177. package/dist/fe/components/UsersPage/components/UserFilters.js +142 -0
  178. package/dist/fe/components/UsersPage/components/UserListItem.js +125 -0
  179. package/dist/fe/components/UsersPage/components/UserListSkeleton.js +40 -0
  180. package/dist/fe/components/UsersPage/components/UsersPage.js +556 -0
  181. package/dist/fe/components/UsersPage/index.js +10 -0
  182. package/dist/fe/components/UsersPage/store/index.js +151 -0
  183. package/dist/fe/components/UsersPage/theme/index.js +231 -0
  184. package/dist/fe/components/UsersPage/types/index.js +1 -0
  185. package/dist/fe/components/VerifyEmailPage/components/VerifyEmailPage.js +290 -0
  186. package/dist/fe/components/VerifyEmailPage/index.js +3 -0
  187. package/dist/fe/components/VerifyEmailPage/store/index.js +45 -0
  188. package/dist/fe/components/VerifyEmailPage/theme/index.js +52 -0
  189. package/dist/fe/components/VerifyEmailPage/types/index.js +1 -0
  190. package/dist/fe/hooks/useNucleusEntity.js +247 -0
  191. package/dist/fe/index.js +28 -110
  192. package/dist/fe/types/index.js +1 -0
  193. package/dist/fe/utils/cn.js +5 -0
  194. package/dist/fe/utils/columnUtils.js +189 -0
  195. package/dist/fe/utils/endpointKeys.js +44 -0
  196. package/dist/index.js +1 -1
  197. package/dist/nucleus.config.d.ts +3 -0
  198. package/dist/src/Client/Proxy/httpProxy.js +1 -0
  199. package/dist/src/Client/Proxy/index.js +1 -1
  200. package/dist/src/Client/Proxy/server.js +1 -0
  201. package/dist/src/Client/Proxy/types.js +1 -0
  202. package/dist/src/Client/Proxy/utils.js +1 -0
  203. package/dist/src/Client/Proxy/wsProxy.js +1 -0
  204. package/package.json +1 -1
  205. package/scripts/build.ts +78 -8
  206. package/dist/dist/index-0yyw12k6.d.ts +0 -8
  207. package/dist/dist/index-tv67j5qd.d.ts +0 -488
  208. package/dist/index-0yyw12k6.js +0 -1
  209. package/dist/index-tv67j5qd.js +0 -1
@@ -0,0 +1,1409 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useGSAP } from "@gsap/react";
4
+ import gsap from "gsap";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { createPortal } from "react-dom";
7
+ import { useNucleusEntity } from "../hooks/useNucleusEntity";
8
+ import { cn } from "../utils/cn";
9
+ import { formatLabel, getColumnType, getDateFilterColumns, getEnumFilterColumns, getEnumValues, getNumericFilterColumns, getSearchableColumns, isArrayColumn, isBooleanColumn, isJsonColumn, isReferenceColumn, shouldExcludeColumn, shouldExcludeFromForm } from "../utils/columnUtils";
10
+ import { Button } from "./Button";
11
+ import { Checkbox } from "./Checkbox";
12
+ import { DataTable } from "./DataTable";
13
+ import { DatePicker } from "./DatePicker";
14
+ import { NucleusTextInput } from "./NucleusTextInput";
15
+ import { RangePicker } from "./RangePicker";
16
+ import { SearchBox } from "./SearchBox";
17
+ import { SelectBox } from "./SelectBox";
18
+ gsap.registerPlugin(useGSAP);
19
+ const theme = {
20
+ container: "w-full h-full flex flex-col min-h-0",
21
+ header: {
22
+ base: "mb-4",
23
+ title: "text-xl font-semibold text-zinc-900 dark:text-white",
24
+ description: "mt-1 text-sm text-zinc-500 dark:text-zinc-400"
25
+ },
26
+ toolbar: {
27
+ base: "flex flex-col gap-3 mb-4",
28
+ row: "flex flex-wrap items-center gap-3",
29
+ search: "flex-1 min-w-[200px] max-w-md",
30
+ searchFields: "flex flex-wrap items-center gap-2",
31
+ filters: "flex flex-wrap items-center gap-2",
32
+ actions: "flex items-center gap-2 ml-auto"
33
+ },
34
+ table: {
35
+ container: "w-full flex-1 overflow-auto rounded-lg border border-zinc-200 dark:border-zinc-700 min-h-0 relative",
36
+ table: "w-full text-sm table-fixed",
37
+ thead: "bg-zinc-50 dark:bg-zinc-800 sticky top-0 z-10",
38
+ th: "px-3 py-2.5 text-left text-xs font-medium text-zinc-600 dark:text-zinc-400 uppercase tracking-wider bg-zinc-50 dark:bg-zinc-800",
39
+ thSortable: "cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-700 select-none",
40
+ tbody: "divide-y divide-zinc-100 dark:divide-zinc-800",
41
+ tr: "hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors",
42
+ trSelected: "bg-blue-50 dark:bg-blue-900/20",
43
+ td: "px-3 py-2.5 text-sm text-zinc-700 dark:text-zinc-300 truncate relative",
44
+ empty: "px-4 py-12 text-center text-zinc-500 dark:text-zinc-400",
45
+ loading: "px-4 py-8 text-center text-zinc-500"
46
+ },
47
+ pagination: {
48
+ base: "flex items-center justify-between py-3 text-sm border-t border-zinc-200 dark:border-zinc-700",
49
+ info: "text-zinc-500 dark:text-zinc-400",
50
+ buttons: "flex items-center gap-2"
51
+ },
52
+ modal: {
53
+ overlay: "fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-in fade-in duration-200",
54
+ container: "w-full max-w-lg rounded-2xl bg-white p-6 shadow-2xl dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 animate-in zoom-in-95 slide-in-from-bottom-4 duration-200",
55
+ header: "mb-6 flex items-center justify-between border-b border-zinc-100 dark:border-zinc-800 pb-4",
56
+ title: "text-lg font-semibold text-zinc-900 dark:text-white",
57
+ closeButton: "rounded-full p-1 text-zinc-500 hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-300"
58
+ },
59
+ button: {
60
+ base: "inline-flex items-center justify-center rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
61
+ primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
62
+ secondary: "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700",
63
+ danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
64
+ success: "bg-green-600 text-white hover:bg-green-700 focus:ring-green-500",
65
+ ghost: "text-zinc-600 hover:bg-zinc-100 dark:text-zinc-400 dark:hover:bg-zinc-800",
66
+ sizes: {
67
+ sm: "px-3 py-1.5 text-sm",
68
+ md: "px-4 py-2 text-sm",
69
+ lg: "px-5 py-2.5 text-base"
70
+ }
71
+ },
72
+ input: {
73
+ base: "w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 placeholder-zinc-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-500",
74
+ label: "mb-1.5 block text-sm font-medium text-zinc-700 dark:text-zinc-300",
75
+ error: "mt-1 text-xs text-red-500 dark:text-red-400"
76
+ },
77
+ select: {
78
+ base: "w-full rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100"
79
+ },
80
+ checkbox: {
81
+ base: "h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-800"
82
+ },
83
+ skeleton: {
84
+ base: "animate-pulse rounded bg-zinc-200 dark:bg-zinc-700",
85
+ row: "h-12"
86
+ }
87
+ };
88
+ export function NucleusEntityShowcase({ entity, apiActions, title, description, pageSize = 20, searchable = true, searchFields: _searchFields, filterable = true, sortable = true, selectable = true, showAdd = true, showEdit = true, showDelete = true, showBulkAdd: _showBulkAdd = false, showBulkDelete = true, columnConfigs, fieldConfigs, excludeColumns = [], excludeFields = [], onRowClick, renderHeader, renderToolbar, className }) {
89
+ const table = entity.table_name;
90
+ const containerRef = useRef(null);
91
+ const [mounted, setMounted] = useState(false);
92
+ const [selectedRows, setSelectedRows] = useState([]);
93
+ const [modalState, setModalState] = useState({
94
+ isOpen: false,
95
+ mode: "add"
96
+ });
97
+ const [formValues, setFormValues] = useState({});
98
+ const [formErrors, setFormErrors] = useState({});
99
+ const [detailModal, setDetailModal] = useState({
100
+ isOpen: false,
101
+ table: "",
102
+ data: null
103
+ });
104
+ useEffect(()=>{
105
+ setMounted(true);
106
+ }, []);
107
+ const { items, meta, isLoading, isLoadingMore, loadData, loadMore, hasMore, addItem, updateItem, deleteItem, bulkDelete, search, setSearch, setSort, filters, setFilters } = useNucleusEntity({
108
+ entity,
109
+ apiActions: apiActions,
110
+ pageSize
111
+ });
112
+ // Base columns added when entity.add_base_columns is true
113
+ const baseColumns = entity.add_base_columns ? [
114
+ {
115
+ name: "id",
116
+ type: "uuid",
117
+ primaryKey: true
118
+ },
119
+ {
120
+ name: "created_at",
121
+ type: "timestamptz"
122
+ },
123
+ {
124
+ name: "updated_at",
125
+ type: "timestamptz"
126
+ }
127
+ ] : [];
128
+ const entityColumnNames = new Set((entity.columns ?? []).map((c)=>c.name));
129
+ const filteredBaseColumns = baseColumns.filter((c)=>!entityColumnNames.has(c.name));
130
+ const columns = [
131
+ ...entity.columns ?? [],
132
+ ...filteredBaseColumns
133
+ ];
134
+ const visibleColumns = columns.filter((col)=>{
135
+ if (shouldExcludeColumn(col)) return false;
136
+ if (excludeColumns.includes(col.name)) return false;
137
+ const config = columnConfigs?.[col.name];
138
+ if (config?.hidden) return false;
139
+ return true;
140
+ });
141
+ const getFormColumns = (mode)=>columns.filter((col)=>{
142
+ if (shouldExcludeFromForm(col, mode)) return false;
143
+ if (excludeFields.includes(col.name)) return false;
144
+ const config = fieldConfigs?.[col.name];
145
+ if (config?.hidden) return false;
146
+ return true;
147
+ });
148
+ const [referenceData, setReferenceData] = useState({});
149
+ const [loadingReferences, setLoadingReferences] = useState({});
150
+ const referenceColumns = useMemo(()=>columns.filter((col)=>isReferenceColumn(col)), [
151
+ columns
152
+ ]);
153
+ const loadedRefsRef = useRef(new Set());
154
+ const loadReferenceData = useCallback((column)=>{
155
+ if (!column.references) return;
156
+ const refTable = column.references.table;
157
+ if (loadedRefsRef.current.has(refTable)) return;
158
+ loadedRefsRef.current.add(refTable);
159
+ const endpointKey = `GET_${refTable.toUpperCase()}`;
160
+ const action = apiActions[endpointKey];
161
+ if (action) {
162
+ action.start({
163
+ payload: {
164
+ limit: 100
165
+ },
166
+ onAfterHandle: (data)=>{
167
+ const response = data;
168
+ const items = response?.data?.items ?? [];
169
+ const options = items.map((item)=>({
170
+ id: String(item.id ?? ""),
171
+ label: String(item.name ?? item.title ?? item.label ?? item.id ?? ""),
172
+ data: item
173
+ }));
174
+ setReferenceData((prev)=>({
175
+ ...prev,
176
+ [refTable]: options
177
+ }));
178
+ setLoadingReferences((prev)=>({
179
+ ...prev,
180
+ [refTable]: false
181
+ }));
182
+ },
183
+ onErrorHandle: ()=>{
184
+ setLoadingReferences((prev)=>({
185
+ ...prev,
186
+ [refTable]: false
187
+ }));
188
+ }
189
+ });
190
+ }
191
+ }, [
192
+ apiActions
193
+ ]);
194
+ const openDetailModal = (table, id)=>{
195
+ const options = referenceData[table] ?? [];
196
+ const found = options.find((opt)=>opt.id === id);
197
+ if (found) {
198
+ setDetailModal({
199
+ isOpen: true,
200
+ table,
201
+ data: found.data
202
+ });
203
+ }
204
+ };
205
+ const closeDetailModal = ()=>{
206
+ setDetailModal({
207
+ isOpen: false,
208
+ table: "",
209
+ data: null
210
+ });
211
+ };
212
+ useEffect(()=>{
213
+ for (const col of referenceColumns){
214
+ loadReferenceData(col);
215
+ }
216
+ }, [
217
+ referenceColumns,
218
+ loadReferenceData
219
+ ]);
220
+ useGSAP(()=>{
221
+ if (containerRef.current) {
222
+ gsap.fromTo(containerRef.current, {
223
+ opacity: 0,
224
+ y: 10
225
+ }, {
226
+ opacity: 1,
227
+ y: 0,
228
+ duration: 0.3,
229
+ ease: "power2.out"
230
+ });
231
+ }
232
+ }, []);
233
+ const handleSearch = (query)=>{
234
+ setSearch(query);
235
+ loadData({
236
+ search: query,
237
+ page: 1
238
+ });
239
+ };
240
+ const handleClearSelection = ()=>{
241
+ setSelectedRows([]);
242
+ };
243
+ const openAddModal = ()=>{
244
+ const initialValues = {};
245
+ for (const col of getFormColumns("add")){
246
+ const enumVals = col.enumValues ?? col.enum?.values;
247
+ if (col.default !== undefined) {
248
+ initialValues[col.name] = col.default;
249
+ } else if (enumVals && enumVals.length > 0) {
250
+ initialValues[col.name] = enumVals[0];
251
+ } else if (col.type === "boolean") {
252
+ initialValues[col.name] = false;
253
+ } else {
254
+ initialValues[col.name] = "";
255
+ }
256
+ }
257
+ setFormValues(initialValues);
258
+ setFormErrors({});
259
+ setModalState({
260
+ isOpen: true,
261
+ mode: "add"
262
+ });
263
+ };
264
+ const openEditModal = (row)=>{
265
+ const normalizedValues = {};
266
+ const rowData = row;
267
+ for (const col of columns){
268
+ const colName = col.name;
269
+ const camelKey = colName.replace(/_([a-z])/g, (_, c)=>c.toUpperCase());
270
+ // For relation columns, look up by foreignKey
271
+ if (col.references?.foreignKey) {
272
+ const fk = col.references.foreignKey;
273
+ if (fk in rowData) {
274
+ normalizedValues[colName] = rowData[fk];
275
+ continue;
276
+ }
277
+ }
278
+ if (colName in rowData) {
279
+ normalizedValues[colName] = rowData[colName];
280
+ } else if (camelKey in rowData) {
281
+ normalizedValues[colName] = rowData[camelKey];
282
+ } else {
283
+ // Try with Id suffix for relations without explicit foreignKey
284
+ const withIdSuffix = `${camelKey}Id`;
285
+ if (withIdSuffix in rowData) {
286
+ normalizedValues[colName] = rowData[withIdSuffix];
287
+ } else {
288
+ normalizedValues[colName] = undefined;
289
+ }
290
+ }
291
+ }
292
+ setFormValues(normalizedValues);
293
+ setFormErrors({});
294
+ setModalState({
295
+ isOpen: true,
296
+ mode: "edit",
297
+ data: row
298
+ });
299
+ };
300
+ const closeModal = ()=>{
301
+ setModalState({
302
+ isOpen: false,
303
+ mode: "add"
304
+ });
305
+ setFormValues({});
306
+ setFormErrors({});
307
+ setSubmitError(null);
308
+ };
309
+ const validateForm = ()=>{
310
+ const errors = {};
311
+ for (const col of getFormColumns(modalState.mode)){
312
+ const value = formValues[col.name];
313
+ const isEmpty = value === undefined || value === null || value === "";
314
+ if (col.notNull && isEmpty) {
315
+ errors[col.name] = "This field is required";
316
+ continue;
317
+ }
318
+ if (!isEmpty && col.validation) {
319
+ if (col.validation.minLength && String(value).length < col.validation.minLength) {
320
+ errors[col.name] = `Minimum ${col.validation.minLength} characters`;
321
+ }
322
+ if (col.validation.maxLength && String(value).length > col.validation.maxLength) {
323
+ errors[col.name] = `Maximum ${col.validation.maxLength} characters`;
324
+ }
325
+ if (col.validation.min !== undefined && Number(value) < col.validation.min) {
326
+ errors[col.name] = `Minimum value is ${col.validation.min}`;
327
+ }
328
+ if (col.validation.max !== undefined && Number(value) > col.validation.max) {
329
+ errors[col.name] = `Maximum value is ${col.validation.max}`;
330
+ }
331
+ }
332
+ }
333
+ setFormErrors(errors);
334
+ return Object.keys(errors).length === 0;
335
+ };
336
+ const [submitError, setSubmitError] = useState(null);
337
+ const prepareDataForSubmit = (data)=>{
338
+ const prepared = {};
339
+ const formCols = getFormColumns(modalState.mode);
340
+ const numericTypes = [
341
+ "integer",
342
+ "smallint",
343
+ "bigint",
344
+ "real",
345
+ "doublePrecision",
346
+ "numeric",
347
+ "decimal",
348
+ "money"
349
+ ];
350
+ for (const col of formCols){
351
+ const value = data[col.name];
352
+ // Use foreignKey for relation columns, otherwise use column name
353
+ const key = col.references?.foreignKey ?? col.name;
354
+ if (value === undefined || value === null || value === "") {
355
+ if (!col.notNull) {
356
+ prepared[key] = null;
357
+ }
358
+ continue;
359
+ }
360
+ if (isArrayColumn(col)) {
361
+ prepared[key] = Array.isArray(value) ? value.join(",") : String(value);
362
+ } else if (numericTypes.includes(col.type)) {
363
+ prepared[key] = Number(value);
364
+ } else {
365
+ prepared[key] = value;
366
+ }
367
+ }
368
+ return prepared;
369
+ };
370
+ const handleSubmit = async ()=>{
371
+ if (!validateForm()) return;
372
+ setSubmitError(null);
373
+ const preparedData = prepareDataForSubmit(formValues);
374
+ try {
375
+ if (modalState.mode === "add") {
376
+ await addItem(preparedData);
377
+ } else {
378
+ const id = modalState.data?.id;
379
+ if (id) {
380
+ await updateItem(id, preparedData);
381
+ }
382
+ }
383
+ closeModal();
384
+ } catch (err) {
385
+ let errorMessage = "An error occurred";
386
+ const extractMessage = (obj)=>{
387
+ if (typeof obj === "string") return obj;
388
+ if (obj && typeof obj === "object") {
389
+ const o = obj;
390
+ if (o.summary) return String(o.summary);
391
+ if (o.message) return String(o.message);
392
+ if (o.errors && Array.isArray(o.errors)) {
393
+ return o.errors.map((e)=>extractMessage(e)).join(", ");
394
+ }
395
+ }
396
+ return JSON.stringify(obj);
397
+ };
398
+ try {
399
+ const error = err;
400
+ if (error.errors && Array.isArray(error.errors) && error.errors.length > 0) {
401
+ errorMessage = error.errors.map((e)=>extractMessage(e)).join(", ");
402
+ } else if (error.message) {
403
+ try {
404
+ const parsed = JSON.parse(error.message);
405
+ errorMessage = extractMessage(parsed);
406
+ } catch {
407
+ errorMessage = error.message;
408
+ }
409
+ } else {
410
+ errorMessage = extractMessage(err);
411
+ }
412
+ } catch {
413
+ errorMessage = String(err);
414
+ }
415
+ setSubmitError(errorMessage);
416
+ }
417
+ };
418
+ const handleDelete = async (id)=>{
419
+ if (window.confirm("Are you sure you want to delete this item?")) {
420
+ await deleteItem(id);
421
+ }
422
+ };
423
+ const handleBulkDelete = async ()=>{
424
+ if (selectedRows.length === 0) return;
425
+ if (window.confirm(`Are you sure you want to delete ${selectedRows.length} items?`)) {
426
+ const ids = selectedRows.map((r)=>r.id);
427
+ await bulkDelete(ids);
428
+ setSelectedRows([]);
429
+ }
430
+ };
431
+ const renderCellValue = (column, value)=>{
432
+ const config = columnConfigs?.[column.name];
433
+ if (config?.cellRenderer) {
434
+ return config.cellRenderer({
435
+ value,
436
+ row: {}
437
+ });
438
+ }
439
+ if (value === null || value === undefined) {
440
+ return /*#__PURE__*/ _jsx("span", {
441
+ className: "text-zinc-400",
442
+ children: "-"
443
+ });
444
+ }
445
+ if (column.type === "boolean") {
446
+ return value ? "Yes" : "No";
447
+ }
448
+ if (column.type === "timestamp" || column.type === "timestamptz" || column.type === "date") {
449
+ return new Date(value).toLocaleDateString();
450
+ }
451
+ if (column.type === "json" || column.type === "jsonb") {
452
+ return /*#__PURE__*/ _jsxs("code", {
453
+ className: "text-xs",
454
+ children: [
455
+ JSON.stringify(value).slice(0, 50),
456
+ "..."
457
+ ]
458
+ });
459
+ }
460
+ if (isReferenceColumn(column) && column.references) {
461
+ const refTable = column.references.table;
462
+ const options = referenceData[refTable] ?? [];
463
+ const found = options.find((opt)=>opt.id === String(value));
464
+ if (found) {
465
+ return /*#__PURE__*/ _jsxs("button", {
466
+ type: "button",
467
+ onClick: (e)=>{
468
+ e.stopPropagation();
469
+ openDetailModal(refTable, found.id);
470
+ },
471
+ className: "group inline-flex items-center gap-1.5 rounded-md border border-zinc-200 bg-zinc-50 px-2 py-1 text-xs font-medium text-zinc-700 transition-all hover:border-zinc-300 hover:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:bg-zinc-700",
472
+ children: [
473
+ /*#__PURE__*/ _jsx("span", {
474
+ className: "max-w-[120px] truncate",
475
+ children: found.label
476
+ }),
477
+ /*#__PURE__*/ _jsx("svg", {
478
+ className: "h-3 w-3 text-zinc-400 transition-transform group-hover:translate-x-0.5 group-hover:text-zinc-600 dark:group-hover:text-zinc-300",
479
+ fill: "none",
480
+ viewBox: "0 0 24 24",
481
+ stroke: "currentColor",
482
+ "aria-hidden": "true",
483
+ children: /*#__PURE__*/ _jsx("path", {
484
+ strokeLinecap: "round",
485
+ strokeLinejoin: "round",
486
+ strokeWidth: 2,
487
+ d: "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
488
+ })
489
+ })
490
+ ]
491
+ });
492
+ }
493
+ return /*#__PURE__*/ _jsx("span", {
494
+ className: "text-zinc-400",
495
+ children: "-"
496
+ });
497
+ }
498
+ return String(value);
499
+ };
500
+ const renderFormField = (column)=>{
501
+ const config = fieldConfigs?.[column.name];
502
+ const label = config?.label ?? formatLabel(column.name);
503
+ const value = formValues[column.name];
504
+ const error = formErrors[column.name];
505
+ const colType = getColumnType(column);
506
+ if (config?.render) {
507
+ return /*#__PURE__*/ _jsxs("div", {
508
+ className: "mb-4",
509
+ children: [
510
+ /*#__PURE__*/ _jsx("label", {
511
+ htmlFor: `field-${column.name}`,
512
+ className: theme.input.label,
513
+ children: label
514
+ }),
515
+ config.render({
516
+ value,
517
+ onChange: (v)=>setFormValues((prev)=>({
518
+ ...prev,
519
+ [column.name]: v
520
+ }))
521
+ }),
522
+ error && /*#__PURE__*/ _jsx("p", {
523
+ className: theme.input.error,
524
+ children: error
525
+ })
526
+ ]
527
+ }, column.name);
528
+ }
529
+ if (colType === "boolean") {
530
+ return /*#__PURE__*/ _jsxs("div", {
531
+ className: "mb-4",
532
+ children: [
533
+ /*#__PURE__*/ _jsx("label", {
534
+ htmlFor: `field-${column.name}`,
535
+ className: theme.input.label,
536
+ children: label
537
+ }),
538
+ /*#__PURE__*/ _jsx(Checkbox, {
539
+ checked: Boolean(value),
540
+ onChange: (checked)=>setFormValues((prev)=>({
541
+ ...prev,
542
+ [column.name]: checked
543
+ })),
544
+ disabled: config?.disabled,
545
+ size: "sm"
546
+ }),
547
+ error && /*#__PURE__*/ _jsx("p", {
548
+ className: theme.input.error,
549
+ children: error
550
+ })
551
+ ]
552
+ }, column.name);
553
+ }
554
+ const enumValues = column.enumValues ?? column.enum?.values;
555
+ if (colType === "select" && enumValues) {
556
+ return /*#__PURE__*/ _jsxs("div", {
557
+ className: "mb-4",
558
+ children: [
559
+ /*#__PURE__*/ _jsx("label", {
560
+ htmlFor: `field-${column.name}`,
561
+ className: theme.input.label,
562
+ children: label
563
+ }),
564
+ /*#__PURE__*/ _jsx(SelectBox, {
565
+ value: String(value ?? ""),
566
+ onChange: (v)=>setFormValues((prev)=>({
567
+ ...prev,
568
+ [column.name]: v
569
+ })),
570
+ options: enumValues.map((opt)=>({
571
+ value: opt,
572
+ label: formatLabel(opt)
573
+ })),
574
+ placeholder: `Select ${label.toLowerCase()}`,
575
+ size: "sm"
576
+ }),
577
+ error && /*#__PURE__*/ _jsx("p", {
578
+ className: theme.input.error,
579
+ children: error
580
+ })
581
+ ]
582
+ }, column.name);
583
+ }
584
+ if (isReferenceColumn(column) && column.references) {
585
+ const refTable = column.references.table;
586
+ const options = referenceData[refTable] ?? [];
587
+ const isLoadingRef = loadingReferences[refTable] ?? false;
588
+ const currentValue = value ? String(value) : undefined;
589
+ return /*#__PURE__*/ _jsxs("div", {
590
+ className: "mb-4",
591
+ children: [
592
+ /*#__PURE__*/ _jsx("label", {
593
+ htmlFor: `ref-${column.name}`,
594
+ className: theme.input.label,
595
+ children: label
596
+ }),
597
+ /*#__PURE__*/ _jsx(SelectBox, {
598
+ value: currentValue,
599
+ onChange: (v)=>setFormValues((prev)=>({
600
+ ...prev,
601
+ [column.name]: v ?? null
602
+ })),
603
+ options: options.map((opt)=>({
604
+ value: opt.id,
605
+ label: opt.label
606
+ })),
607
+ placeholder: isLoadingRef ? "Loading..." : `Select ${label.toLowerCase()}`,
608
+ size: "sm",
609
+ clearable: true
610
+ }),
611
+ error && /*#__PURE__*/ _jsx("p", {
612
+ className: theme.input.error,
613
+ children: error
614
+ })
615
+ ]
616
+ }, column.name);
617
+ }
618
+ if (isJsonColumn(column)) {
619
+ const jsonValue = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value ?? "{}");
620
+ return /*#__PURE__*/ _jsxs("div", {
621
+ className: "mb-4",
622
+ children: [
623
+ /*#__PURE__*/ _jsx("label", {
624
+ htmlFor: `json-${column.name}`,
625
+ className: theme.input.label,
626
+ children: label
627
+ }),
628
+ /*#__PURE__*/ _jsx("textarea", {
629
+ id: `json-${column.name}`,
630
+ value: jsonValue,
631
+ onChange: (e)=>{
632
+ try {
633
+ const parsed = JSON.parse(e.target.value);
634
+ setFormValues((prev)=>({
635
+ ...prev,
636
+ [column.name]: parsed
637
+ }));
638
+ setFormErrors((prev)=>{
639
+ const next = {
640
+ ...prev
641
+ };
642
+ delete next[column.name];
643
+ return next;
644
+ });
645
+ } catch {
646
+ setFormValues((prev)=>({
647
+ ...prev,
648
+ [column.name]: e.target.value
649
+ }));
650
+ setFormErrors((prev)=>({
651
+ ...prev,
652
+ [column.name]: "Invalid JSON"
653
+ }));
654
+ }
655
+ },
656
+ placeholder: config?.placeholder ?? "Enter JSON...",
657
+ className: cn(theme.input.base, "min-h-[100px] font-mono text-xs"),
658
+ disabled: config?.disabled,
659
+ readOnly: config?.readOnly
660
+ }),
661
+ error && /*#__PURE__*/ _jsx("p", {
662
+ className: theme.input.error,
663
+ children: error
664
+ })
665
+ ]
666
+ }, column.name);
667
+ }
668
+ if (isArrayColumn(column)) {
669
+ const arrayValue = Array.isArray(value) ? value : [];
670
+ const inputId = `array-${column.name}`;
671
+ return /*#__PURE__*/ _jsxs("div", {
672
+ className: "mb-4",
673
+ children: [
674
+ /*#__PURE__*/ _jsx("label", {
675
+ htmlFor: inputId,
676
+ className: theme.input.label,
677
+ children: label
678
+ }),
679
+ /*#__PURE__*/ _jsxs("div", {
680
+ className: "flex flex-wrap gap-1 rounded-lg border border-zinc-300 bg-white p-2 dark:border-zinc-600 dark:bg-zinc-800",
681
+ children: [
682
+ arrayValue.map((tag, idx)=>/*#__PURE__*/ _jsxs("span", {
683
+ className: "inline-flex items-center gap-1 rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-800 dark:bg-blue-900 dark:text-blue-200",
684
+ children: [
685
+ tag,
686
+ /*#__PURE__*/ _jsx("button", {
687
+ type: "button",
688
+ onClick: ()=>{
689
+ const newArr = arrayValue.filter((_, i)=>i !== idx);
690
+ setFormValues((prev)=>({
691
+ ...prev,
692
+ [column.name]: newArr
693
+ }));
694
+ },
695
+ className: "ml-1 text-blue-600 hover:text-blue-800 dark:text-blue-300",
696
+ children: "×"
697
+ })
698
+ ]
699
+ }, idx)),
700
+ /*#__PURE__*/ _jsx("input", {
701
+ id: inputId,
702
+ type: "text",
703
+ className: "min-w-[100px] flex-1 border-none bg-transparent text-sm outline-none",
704
+ placeholder: arrayValue.length === 0 ? config?.placeholder ?? "Type and press Enter" : "",
705
+ onKeyDown: (e)=>{
706
+ if (e.key === "Enter") {
707
+ e.preventDefault();
708
+ const input = e.currentTarget;
709
+ const val = input.value.trim();
710
+ if (val) {
711
+ setFormValues((prev)=>({
712
+ ...prev,
713
+ [column.name]: [
714
+ ...arrayValue,
715
+ val
716
+ ]
717
+ }));
718
+ input.value = "";
719
+ }
720
+ }
721
+ },
722
+ disabled: config?.disabled,
723
+ readOnly: config?.readOnly
724
+ })
725
+ ]
726
+ }),
727
+ /*#__PURE__*/ _jsx("p", {
728
+ className: "mt-1 text-xs text-zinc-500",
729
+ children: "Press Enter to add"
730
+ }),
731
+ error && /*#__PURE__*/ _jsx("p", {
732
+ className: theme.input.error,
733
+ children: error
734
+ })
735
+ ]
736
+ }, column.name);
737
+ }
738
+ return /*#__PURE__*/ _jsxs("div", {
739
+ className: "mb-4",
740
+ children: [
741
+ /*#__PURE__*/ _jsx(NucleusTextInput, {
742
+ type: colType === "number" ? "number" : "text",
743
+ label: label,
744
+ value: String(value ?? ""),
745
+ onChange: (newValue)=>{
746
+ const finalValue = colType === "number" ? newValue === "" ? "" : Number(newValue) : newValue;
747
+ setFormValues((prev)=>({
748
+ ...prev,
749
+ [column.name]: finalValue
750
+ }));
751
+ },
752
+ placeholder: config?.placeholder ?? `Enter ${label.toLowerCase()}`,
753
+ disabled: config?.disabled,
754
+ readOnly: config?.readOnly
755
+ }),
756
+ error && /*#__PURE__*/ _jsx("p", {
757
+ className: theme.input.error,
758
+ children: error
759
+ })
760
+ ]
761
+ }, column.name);
762
+ };
763
+ const tableContainerRef = useRef(null);
764
+ // Get categorized columns using type-safe utilities
765
+ const searchableColumns = getSearchableColumns(columns);
766
+ const enumFilterColumns = getEnumFilterColumns(columns);
767
+ const numericFilterColumns = getNumericFilterColumns(columns);
768
+ const dateFilterColumns = getDateFilterColumns(columns);
769
+ const [searchFieldsEnabled, setSearchFieldsEnabled] = useState(()=>{
770
+ const initial = {};
771
+ for (const col of searchableColumns){
772
+ initial[col.name] = true;
773
+ }
774
+ return initial;
775
+ });
776
+ const loadMoreRef = useRef(loadMore);
777
+ loadMoreRef.current = loadMore;
778
+ const stateRef = useRef({
779
+ hasMore,
780
+ isLoadingMore,
781
+ isLoading
782
+ });
783
+ stateRef.current = {
784
+ hasMore,
785
+ isLoadingMore,
786
+ isLoading
787
+ };
788
+ useEffect(()=>{
789
+ const container = tableContainerRef.current;
790
+ if (!container) return;
791
+ const checkAndLoadMore = ()=>{
792
+ const { scrollTop, scrollHeight, clientHeight } = container;
793
+ const threshold = 100;
794
+ const needsMore = scrollHeight <= clientHeight || scrollHeight - scrollTop - clientHeight < threshold;
795
+ const { hasMore, isLoadingMore, isLoading } = stateRef.current;
796
+ if (needsMore && hasMore && !isLoadingMore && !isLoading) {
797
+ loadMoreRef.current();
798
+ }
799
+ };
800
+ checkAndLoadMore();
801
+ container.addEventListener("scroll", checkAndLoadMore);
802
+ return ()=>container.removeEventListener("scroll", checkAndLoadMore);
803
+ }, [
804
+ items.length
805
+ ]);
806
+ const handleFilterChange = (field, value)=>{
807
+ if (!value) {
808
+ setFilters(filters.filter((f)=>f.field !== field));
809
+ } else {
810
+ const existing = filters.find((f)=>f.field === field);
811
+ if (existing) {
812
+ setFilters(filters.map((f)=>f.field === field ? {
813
+ ...f,
814
+ value
815
+ } : f));
816
+ } else {
817
+ setFilters([
818
+ ...filters,
819
+ {
820
+ field,
821
+ operator: "eq",
822
+ value
823
+ }
824
+ ]);
825
+ }
826
+ }
827
+ loadData({
828
+ filters: value ? [
829
+ ...filters.filter((f)=>f.field !== field),
830
+ {
831
+ field,
832
+ operator: "eq",
833
+ value
834
+ }
835
+ ] : filters.filter((f)=>f.field !== field),
836
+ page: 1
837
+ });
838
+ };
839
+ const getFilterValue = (field)=>{
840
+ const f = filters.find((item)=>item.field === field);
841
+ return f ? String(f.value) : "";
842
+ };
843
+ const toggleSearchField = (field)=>{
844
+ setSearchFieldsEnabled((prev)=>({
845
+ ...prev,
846
+ [field]: !prev[field]
847
+ }));
848
+ };
849
+ const getColumnWidth = (col)=>{
850
+ const config = columnConfigs?.[col.name];
851
+ if (config?.width) return `${config.width}px`;
852
+ switch(col.type){
853
+ case "uuid":
854
+ return "140px";
855
+ case "boolean":
856
+ return "80px";
857
+ case "integer":
858
+ case "smallint":
859
+ case "bigint":
860
+ return "90px";
861
+ case "numeric":
862
+ case "decimal":
863
+ case "real":
864
+ case "doublePrecision":
865
+ return "100px";
866
+ case "timestamp":
867
+ case "timestamptz":
868
+ case "date":
869
+ return "120px";
870
+ case "json":
871
+ case "jsonb":
872
+ return "100px";
873
+ default:
874
+ return "150px";
875
+ }
876
+ };
877
+ const displayTitle = title ?? formatLabel(table);
878
+ const getRowValue = (row, colName, column)=>{
879
+ const rowData = row;
880
+ // For relation columns, look up by foreignKey
881
+ if (column?.references?.foreignKey) {
882
+ const fk = column.references.foreignKey;
883
+ if (fk in rowData) return rowData[fk];
884
+ }
885
+ // Direct lookup
886
+ if (colName in rowData) return rowData[colName];
887
+ // Try camelCase conversion
888
+ const camelKey = colName.replace(/_([a-z])/g, (_, c)=>c.toUpperCase());
889
+ if (camelKey in rowData) return rowData[camelKey];
890
+ // Try adding Id suffix for relations
891
+ const withIdSuffix = `${camelKey}Id`;
892
+ if (withIdSuffix in rowData) return rowData[withIdSuffix];
893
+ return undefined;
894
+ };
895
+ return /*#__PURE__*/ _jsxs("div", {
896
+ ref: containerRef,
897
+ className: cn(theme.container, className),
898
+ children: [
899
+ renderHeader ? renderHeader() : /*#__PURE__*/ _jsxs("div", {
900
+ className: theme.header.base,
901
+ children: [
902
+ /*#__PURE__*/ _jsx("h2", {
903
+ className: theme.header.title,
904
+ children: displayTitle
905
+ }),
906
+ description && /*#__PURE__*/ _jsx("p", {
907
+ className: theme.header.description,
908
+ children: description
909
+ })
910
+ ]
911
+ }),
912
+ searchable && /*#__PURE__*/ _jsxs("div", {
913
+ className: "mb-6 rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-4",
914
+ children: [
915
+ /*#__PURE__*/ _jsx(SearchBox, {
916
+ value: search,
917
+ onChange: setSearch,
918
+ onSearch: handleSearch,
919
+ placeholder: `Search ${displayTitle.toLowerCase()}...`,
920
+ loading: isLoading,
921
+ debounceMs: 400,
922
+ disableDropdown: true,
923
+ showSearchIcon: true,
924
+ showClearButton: true
925
+ }),
926
+ searchableColumns.length > 0 && /*#__PURE__*/ _jsxs("div", {
927
+ className: "mt-4 flex flex-wrap items-center gap-2",
928
+ children: [
929
+ /*#__PURE__*/ _jsx("span", {
930
+ className: "text-xs font-medium text-zinc-600 dark:text-zinc-400",
931
+ children: "Search in:"
932
+ }),
933
+ searchableColumns.map((col)=>/*#__PURE__*/ _jsxs("label", {
934
+ htmlFor: `search-field-${col.name}`,
935
+ className: "flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-zinc-100 dark:bg-zinc-800 hover:bg-zinc-200 dark:hover:bg-zinc-700 cursor-pointer transition-colors",
936
+ children: [
937
+ /*#__PURE__*/ _jsx(Checkbox, {
938
+ checked: searchFieldsEnabled[col.name] ?? false,
939
+ onChange: ()=>toggleSearchField(col.name),
940
+ size: "sm"
941
+ }),
942
+ /*#__PURE__*/ _jsx("span", {
943
+ className: "text-xs font-medium text-zinc-700 dark:text-zinc-300",
944
+ children: formatLabel(col.name)
945
+ })
946
+ ]
947
+ }, col.name))
948
+ ]
949
+ })
950
+ ]
951
+ }),
952
+ (showAdd || selectedRows.length > 0) && /*#__PURE__*/ _jsxs("div", {
953
+ className: "mb-6 flex items-center gap-3",
954
+ children: [
955
+ selectedRows.length > 0 && showBulkDelete && /*#__PURE__*/ _jsxs(Button, {
956
+ variant: "danger",
957
+ size: "sm",
958
+ onClick: handleBulkDelete,
959
+ children: [
960
+ "Delete (",
961
+ selectedRows.length,
962
+ ")"
963
+ ]
964
+ }),
965
+ showAdd && !entity.excluded_methods?.includes("POST") && /*#__PURE__*/ _jsxs(Button, {
966
+ variant: "success",
967
+ size: "sm",
968
+ onClick: openAddModal,
969
+ children: [
970
+ "Add",
971
+ " ",
972
+ formatLabel(table.endsWith("s") ? table.slice(0, -1) : table)
973
+ ]
974
+ })
975
+ ]
976
+ }),
977
+ filterable && (enumFilterColumns.length > 0 || numericFilterColumns.length > 0 || dateFilterColumns.length > 0) && /*#__PURE__*/ _jsxs("div", {
978
+ className: "rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 p-6",
979
+ children: [
980
+ /*#__PURE__*/ _jsxs("div", {
981
+ className: "mb-6 flex items-center justify-between",
982
+ children: [
983
+ /*#__PURE__*/ _jsx("h3", {
984
+ className: "text-sm font-semibold text-zinc-900 dark:text-white",
985
+ children: "Filters"
986
+ }),
987
+ filters.length > 0 && /*#__PURE__*/ _jsx("button", {
988
+ type: "button",
989
+ onClick: ()=>setFilters([]),
990
+ className: "text-xs px-2 py-1 rounded text-emerald-600 hover:bg-emerald-50 dark:hover:bg-emerald-950 dark:text-emerald-400 font-medium transition-colors",
991
+ children: "Clear"
992
+ })
993
+ ]
994
+ }),
995
+ /*#__PURE__*/ _jsxs("div", {
996
+ className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8",
997
+ children: [
998
+ enumFilterColumns.map((col)=>{
999
+ const enumValues = getEnumValues(col);
1000
+ const options = isBooleanColumn(col) ? [
1001
+ {
1002
+ value: "",
1003
+ label: "All"
1004
+ },
1005
+ {
1006
+ value: "true",
1007
+ label: "Yes"
1008
+ },
1009
+ {
1010
+ value: "false",
1011
+ label: "No"
1012
+ }
1013
+ ] : [
1014
+ {
1015
+ value: "",
1016
+ label: "All"
1017
+ },
1018
+ ...enumValues.map((v)=>({
1019
+ value: v,
1020
+ label: formatLabel(v)
1021
+ }))
1022
+ ];
1023
+ return /*#__PURE__*/ _jsxs("div", {
1024
+ className: "flex flex-col gap-2",
1025
+ children: [
1026
+ /*#__PURE__*/ _jsx("span", {
1027
+ className: "text-xs font-medium text-zinc-600 dark:text-zinc-400",
1028
+ children: formatLabel(col.name)
1029
+ }),
1030
+ /*#__PURE__*/ _jsx(SelectBox, {
1031
+ value: getFilterValue(col.name) || undefined,
1032
+ onChange: (v)=>handleFilterChange(col.name, v ?? ""),
1033
+ options: options,
1034
+ placeholder: "All",
1035
+ size: "sm",
1036
+ clearable: true
1037
+ })
1038
+ ]
1039
+ }, col.name);
1040
+ }),
1041
+ numericFilterColumns.map((col)=>{
1042
+ const minVal = Number(getFilterValue(`${col.name}_min`)) || 0;
1043
+ const maxVal = Number(getFilterValue(`${col.name}_max`)) || 10000;
1044
+ return /*#__PURE__*/ _jsxs("div", {
1045
+ className: "flex flex-col gap-3",
1046
+ children: [
1047
+ /*#__PURE__*/ _jsxs("div", {
1048
+ className: "flex items-center justify-between",
1049
+ children: [
1050
+ /*#__PURE__*/ _jsx("span", {
1051
+ className: "text-xs font-medium text-zinc-600 dark:text-zinc-400",
1052
+ children: formatLabel(col.name)
1053
+ }),
1054
+ /*#__PURE__*/ _jsxs("span", {
1055
+ className: "text-xs font-semibold text-zinc-700 dark:text-zinc-300",
1056
+ children: [
1057
+ minVal,
1058
+ " - ",
1059
+ maxVal
1060
+ ]
1061
+ })
1062
+ ]
1063
+ }),
1064
+ /*#__PURE__*/ _jsx(RangePicker, {
1065
+ min: 0,
1066
+ max: 10000,
1067
+ step: col.type === "numeric" || col.type === "decimal" ? 0.01 : 1,
1068
+ value: {
1069
+ min: minVal,
1070
+ max: maxVal
1071
+ },
1072
+ onChange: (v)=>{
1073
+ handleFilterChange(`${col.name}_min`, String(v.min));
1074
+ handleFilterChange(`${col.name}_max`, String(v.max));
1075
+ },
1076
+ showTooltip: true,
1077
+ size: "md"
1078
+ })
1079
+ ]
1080
+ }, col.name);
1081
+ }),
1082
+ dateFilterColumns.map((col)=>/*#__PURE__*/ _jsxs("div", {
1083
+ className: "flex flex-col gap-2",
1084
+ children: [
1085
+ /*#__PURE__*/ _jsx("span", {
1086
+ className: "text-xs font-medium text-zinc-600 dark:text-zinc-400",
1087
+ children: formatLabel(col.name)
1088
+ }),
1089
+ /*#__PURE__*/ _jsx(DatePicker, {
1090
+ mode: "range",
1091
+ value: {
1092
+ start: getFilterValue(`${col.name}_from`) ? new Date(getFilterValue(`${col.name}_from`)) : null,
1093
+ end: getFilterValue(`${col.name}_to`) ? new Date(getFilterValue(`${col.name}_to`)) : null
1094
+ },
1095
+ onChange: (v)=>{
1096
+ handleFilterChange(`${col.name}_from`, v.start?.toISOString().split("T")[0] ?? "");
1097
+ handleFilterChange(`${col.name}_to`, v.end?.toISOString().split("T")[0] ?? "");
1098
+ },
1099
+ placeholder: "Select range"
1100
+ })
1101
+ ]
1102
+ }, col.name))
1103
+ ]
1104
+ })
1105
+ ]
1106
+ }),
1107
+ renderToolbar?.({
1108
+ selectedRows,
1109
+ onClearSelection: handleClearSelection
1110
+ }),
1111
+ /*#__PURE__*/ _jsx("div", {
1112
+ className: "mt-6 flex-1 min-h-0 overflow-x-auto",
1113
+ children: /*#__PURE__*/ _jsx(DataTable, {
1114
+ data: items,
1115
+ columns: visibleColumns.map((col)=>{
1116
+ const config = columnConfigs?.[col.name];
1117
+ const colWidth = getColumnWidth(col);
1118
+ return {
1119
+ key: `col_${col.name}`,
1120
+ header: config?.header ?? formatLabel(col.name),
1121
+ width: Number.parseInt(colWidth, 10) || 150,
1122
+ sortable: sortable && config?.sortable !== false,
1123
+ resizable: true,
1124
+ cellRenderer: ({ row })=>{
1125
+ const cellValue = getRowValue(row, col.name, col);
1126
+ return renderCellValue(col, cellValue);
1127
+ }
1128
+ };
1129
+ }),
1130
+ actionColumns: showEdit || showDelete ? [
1131
+ {
1132
+ key: "actions",
1133
+ header: "Actions",
1134
+ width: 120,
1135
+ renderer: ({ row })=>{
1136
+ const rowId = row.id ?? "";
1137
+ return /*#__PURE__*/ _jsxs("div", {
1138
+ className: "flex items-center gap-2",
1139
+ children: [
1140
+ showEdit && !entity.excluded_methods?.includes("PUT") && /*#__PURE__*/ _jsx(Button, {
1141
+ variant: "outline",
1142
+ size: "xs",
1143
+ onClick: ()=>openEditModal(row),
1144
+ children: "Edit"
1145
+ }),
1146
+ showDelete && !entity.excluded_methods?.includes("DELETE") && /*#__PURE__*/ _jsx(Button, {
1147
+ variant: "ghost",
1148
+ size: "xs",
1149
+ onClick: ()=>handleDelete(rowId),
1150
+ className: "text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-950",
1151
+ children: "Delete"
1152
+ })
1153
+ ]
1154
+ });
1155
+ }
1156
+ }
1157
+ ] : undefined,
1158
+ keyExtractor: (row)=>row.id ?? String(Math.random()),
1159
+ callbacks: {
1160
+ onSort: (columnKey, direction)=>{
1161
+ if (direction) {
1162
+ setSort([
1163
+ {
1164
+ field: String(columnKey),
1165
+ direction
1166
+ }
1167
+ ]);
1168
+ loadData({
1169
+ sort: [
1170
+ {
1171
+ field: String(columnKey),
1172
+ direction
1173
+ }
1174
+ ]
1175
+ });
1176
+ } else {
1177
+ setSort([]);
1178
+ loadData({
1179
+ sort: []
1180
+ });
1181
+ }
1182
+ },
1183
+ onLoadMore: loadMore,
1184
+ onCellClick: onRowClick ? ({ row })=>{
1185
+ onRowClick(row);
1186
+ } : undefined
1187
+ },
1188
+ config: {
1189
+ hasClickAction: Boolean(onRowClick),
1190
+ isFrontendSort: false,
1191
+ enableInfiniteScroll: true,
1192
+ autoFetchUntilScroll: true,
1193
+ enableSelection: selectable
1194
+ },
1195
+ isLoading: isLoading && items.length === 0,
1196
+ isPending: isLoading && items.length === 0,
1197
+ isLoadingMore: isLoadingMore,
1198
+ hasMoreData: hasMore,
1199
+ emptyMessage: `No ${displayTitle.toLowerCase()} found`,
1200
+ selectionToolbar: selectable ? ({ selectedData, onClearSelection: clearSel })=>/*#__PURE__*/ _jsx(_Fragment, {
1201
+ children: showBulkDelete && selectedData.length > 0 && /*#__PURE__*/ _jsxs(Button, {
1202
+ variant: "danger",
1203
+ size: "sm",
1204
+ onClick: ()=>{
1205
+ const ids = selectedData.map((r)=>r.id).filter(Boolean);
1206
+ if (ids.length > 0 && window.confirm(`Delete ${ids.length} items?`)) {
1207
+ bulkDelete(ids).then(()=>clearSel());
1208
+ }
1209
+ },
1210
+ children: [
1211
+ "Delete (",
1212
+ selectedData.length,
1213
+ ")"
1214
+ ]
1215
+ })
1216
+ }) : undefined
1217
+ })
1218
+ }),
1219
+ (meta || isLoadingMore) && /*#__PURE__*/ _jsxs("div", {
1220
+ className: theme.pagination.base,
1221
+ children: [
1222
+ /*#__PURE__*/ _jsx("span", {
1223
+ className: theme.pagination.info,
1224
+ children: meta ? `Showing ${items.length} of ${meta.totalItems} items` : ""
1225
+ }),
1226
+ isLoadingMore && /*#__PURE__*/ _jsxs("span", {
1227
+ className: "flex items-center gap-2 text-zinc-500 dark:text-zinc-400",
1228
+ children: [
1229
+ /*#__PURE__*/ _jsxs("svg", {
1230
+ className: "h-4 w-4 animate-spin",
1231
+ viewBox: "0 0 24 24",
1232
+ fill: "none",
1233
+ "aria-hidden": "true",
1234
+ children: [
1235
+ /*#__PURE__*/ _jsx("circle", {
1236
+ className: "opacity-25",
1237
+ cx: "12",
1238
+ cy: "12",
1239
+ r: "10",
1240
+ stroke: "currentColor",
1241
+ strokeWidth: "4"
1242
+ }),
1243
+ /*#__PURE__*/ _jsx("path", {
1244
+ className: "opacity-75",
1245
+ fill: "currentColor",
1246
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
1247
+ })
1248
+ ]
1249
+ }),
1250
+ "Loading more..."
1251
+ ]
1252
+ })
1253
+ ]
1254
+ }),
1255
+ mounted && modalState.isOpen && /*#__PURE__*/ createPortal(/*#__PURE__*/ _jsx("div", {
1256
+ style: {
1257
+ position: "fixed",
1258
+ inset: 0,
1259
+ zIndex: 9999,
1260
+ display: "flex",
1261
+ alignItems: "center",
1262
+ justifyContent: "center",
1263
+ backgroundColor: "rgba(0,0,0,0.6)",
1264
+ backdropFilter: "blur(4px)",
1265
+ padding: "1rem"
1266
+ },
1267
+ onClick: closeModal,
1268
+ onKeyDown: (e)=>e.key === "Escape" && closeModal(),
1269
+ role: "dialog",
1270
+ "aria-modal": "true",
1271
+ children: /*#__PURE__*/ _jsxs("div", {
1272
+ className: "w-full max-w-lg rounded-2xl bg-white p-6 shadow-2xl dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700",
1273
+ onClick: (e)=>e.stopPropagation(),
1274
+ onKeyDown: (e)=>{
1275
+ e.stopPropagation();
1276
+ if (e.key === "Escape") closeModal();
1277
+ },
1278
+ role: "document",
1279
+ children: [
1280
+ /*#__PURE__*/ _jsxs("div", {
1281
+ className: theme.modal.header,
1282
+ children: [
1283
+ /*#__PURE__*/ _jsxs("h3", {
1284
+ className: theme.modal.title,
1285
+ children: [
1286
+ modalState.mode === "add" ? "Add" : "Edit",
1287
+ " ",
1288
+ formatLabel(table.endsWith("s") ? table.slice(0, -1) : table)
1289
+ ]
1290
+ }),
1291
+ /*#__PURE__*/ _jsx(Button, {
1292
+ variant: "ghost",
1293
+ size: "sm",
1294
+ onClick: closeModal,
1295
+ children: "✕"
1296
+ })
1297
+ ]
1298
+ }),
1299
+ /*#__PURE__*/ _jsxs("form", {
1300
+ onSubmit: (e)=>{
1301
+ e.preventDefault();
1302
+ handleSubmit();
1303
+ },
1304
+ children: [
1305
+ submitError && /*#__PURE__*/ _jsx("div", {
1306
+ className: "mb-4 rounded-lg bg-red-50 p-3 text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400",
1307
+ children: submitError
1308
+ }),
1309
+ getFormColumns(modalState.mode).map((col)=>renderFormField(col)),
1310
+ /*#__PURE__*/ _jsxs("div", {
1311
+ className: "mt-6 flex justify-end gap-2",
1312
+ children: [
1313
+ /*#__PURE__*/ _jsx(Button, {
1314
+ variant: "secondary",
1315
+ size: "sm",
1316
+ onClick: closeModal,
1317
+ children: "Cancel"
1318
+ }),
1319
+ /*#__PURE__*/ _jsx(Button, {
1320
+ variant: "primary",
1321
+ size: "sm",
1322
+ type: "submit",
1323
+ children: modalState.mode === "add" ? "Create" : "Update"
1324
+ })
1325
+ ]
1326
+ })
1327
+ ]
1328
+ })
1329
+ ]
1330
+ })
1331
+ }), document.body),
1332
+ mounted && detailModal.isOpen && detailModal.data && /*#__PURE__*/ createPortal(/*#__PURE__*/ _jsx("div", {
1333
+ style: {
1334
+ position: "fixed",
1335
+ inset: 0,
1336
+ zIndex: 9999,
1337
+ display: "flex",
1338
+ alignItems: "center",
1339
+ justifyContent: "center",
1340
+ backgroundColor: "rgba(0,0,0,0.6)",
1341
+ backdropFilter: "blur(4px)",
1342
+ padding: "1rem"
1343
+ },
1344
+ onClick: closeDetailModal,
1345
+ onKeyDown: (e)=>e.key === "Escape" && closeDetailModal(),
1346
+ role: "dialog",
1347
+ "aria-modal": "true",
1348
+ children: /*#__PURE__*/ _jsxs("div", {
1349
+ className: "w-full max-w-lg rounded-2xl bg-white p-6 shadow-2xl dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700",
1350
+ onClick: (e)=>e.stopPropagation(),
1351
+ onKeyDown: (e)=>{
1352
+ e.stopPropagation();
1353
+ if (e.key === "Escape") closeDetailModal();
1354
+ },
1355
+ role: "document",
1356
+ children: [
1357
+ /*#__PURE__*/ _jsxs("div", {
1358
+ className: theme.modal.header,
1359
+ children: [
1360
+ /*#__PURE__*/ _jsxs("h3", {
1361
+ className: theme.modal.title,
1362
+ children: [
1363
+ formatLabel(detailModal.table.endsWith("s") ? detailModal.table.slice(0, -1) : detailModal.table),
1364
+ " ",
1365
+ "Details"
1366
+ ]
1367
+ }),
1368
+ /*#__PURE__*/ _jsx(Button, {
1369
+ variant: "ghost",
1370
+ size: "sm",
1371
+ onClick: closeDetailModal,
1372
+ children: "✕"
1373
+ })
1374
+ ]
1375
+ }),
1376
+ /*#__PURE__*/ _jsx("div", {
1377
+ className: "space-y-3",
1378
+ children: Object.entries(detailModal.data).map(([key, value])=>{
1379
+ if (key === "id" || key.endsWith("At") || key.endsWith("By")) return null;
1380
+ return /*#__PURE__*/ _jsxs("div", {
1381
+ className: "flex flex-col gap-1",
1382
+ children: [
1383
+ /*#__PURE__*/ _jsx("span", {
1384
+ className: "text-xs font-medium text-zinc-500 dark:text-zinc-400",
1385
+ children: formatLabel(key)
1386
+ }),
1387
+ /*#__PURE__*/ _jsx("span", {
1388
+ className: "text-sm text-zinc-900 dark:text-zinc-100",
1389
+ children: value === null || value === undefined ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value)
1390
+ })
1391
+ ]
1392
+ }, key);
1393
+ })
1394
+ }),
1395
+ /*#__PURE__*/ _jsx("div", {
1396
+ className: "mt-6 flex justify-end",
1397
+ children: /*#__PURE__*/ _jsx(Button, {
1398
+ variant: "secondary",
1399
+ size: "sm",
1400
+ onClick: closeDetailModal,
1401
+ children: "Close"
1402
+ })
1403
+ })
1404
+ ]
1405
+ })
1406
+ }), document.body)
1407
+ ]
1408
+ });
1409
+ }