@goplusvn/core 0.1.0 → 0.1.1

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 (369) hide show
  1. package/package.json +2 -1
  2. package/src/assets/erp_wallpaper.png +0 -0
  3. package/src/assets/goeat_logo.png +0 -0
  4. package/src/audit/audit-manager.ts +139 -0
  5. package/src/audit/index.ts +11 -0
  6. package/src/audit/memory-audit-logger.ts +86 -0
  7. package/src/audit/types.ts +50 -0
  8. package/src/auth/auth-service.ts +97 -0
  9. package/src/auth/index.ts +266 -0
  10. package/src/code-generation/index.ts +69 -0
  11. package/src/configs/auth-routes.ts +17 -0
  12. package/src/configs/crud.ts +136 -0
  13. package/src/configs/data/navigations.ts +781 -0
  14. package/src/configs/data/oauth-links.ts +10 -0
  15. package/src/configs/entities/material-categories.config.ts +125 -0
  16. package/src/configs/i18n.ts +12 -0
  17. package/src/configs/index.ts +26 -0
  18. package/src/configs/status.ts +25 -0
  19. package/src/configs/themes.ts +100 -0
  20. package/src/crud/components/crud-bulk-actions.tsx +91 -0
  21. package/src/crud/components/crud-card-view.tsx +241 -0
  22. package/src/crud/components/crud-context.tsx +122 -0
  23. package/src/crud/components/crud-delete-dialog.tsx +145 -0
  24. package/src/crud/components/crud-dialog.tsx +406 -0
  25. package/src/crud/components/crud-empty-state.tsx +104 -0
  26. package/src/crud/components/crud-export-button.tsx +170 -0
  27. package/src/crud/components/crud-field-renderer.tsx +653 -0
  28. package/src/crud/components/crud-filter-chips.tsx +102 -0
  29. package/src/crud/components/crud-filters/checkbox-filter.tsx +97 -0
  30. package/src/crud/components/crud-filters/datetime-filter.tsx +83 -0
  31. package/src/crud/components/crud-filters/filter-builder.tsx +66 -0
  32. package/src/crud/components/crud-filters/index.tsx +76 -0
  33. package/src/crud/components/crud-filters/radio-filter.tsx +86 -0
  34. package/src/crud/components/crud-filters/select-filter.tsx +141 -0
  35. package/src/crud/components/crud-filters/text-filter.tsx +86 -0
  36. package/src/crud/components/crud-form.tsx +642 -0
  37. package/src/crud/components/crud-import-dialog.tsx +440 -0
  38. package/src/crud/components/crud-infinite-scroll.tsx +116 -0
  39. package/src/crud/components/crud-page.tsx +1017 -0
  40. package/src/crud/components/crud-provider.tsx +277 -0
  41. package/src/crud/components/crud-row-actions.tsx +189 -0
  42. package/src/crud/components/crud-search.tsx +82 -0
  43. package/src/crud/components/crud-sheet.tsx +336 -0
  44. package/src/crud/components/crud-table-skeleton.tsx +26 -0
  45. package/src/crud/components/crud-table-toolbar.tsx +91 -0
  46. package/src/crud/components/crud-table.tsx +352 -0
  47. package/src/crud/components/crud-virtual-table.tsx +55 -0
  48. package/src/crud/components/index.tsx +20 -0
  49. package/src/crud/crud-filters/checkbox-filter.tsx +87 -0
  50. package/src/crud/crud-filters/datetime-filter.tsx +82 -0
  51. package/src/crud/crud-filters/filter-builder.tsx +64 -0
  52. package/src/crud/crud-filters/index.tsx +78 -0
  53. package/src/crud/crud-filters/radio-filter.tsx +79 -0
  54. package/src/crud/crud-filters/select-filter.tsx +148 -0
  55. package/src/crud/crud-filters/text-filter.tsx +81 -0
  56. package/src/crud/index.ts +43 -0
  57. package/src/crud/lib/crud-service.test.ts +334 -0
  58. package/src/crud/lib/crud-service.ts +358 -0
  59. package/src/crud/lib/crud-utils.test.ts +354 -0
  60. package/src/crud/lib/crud-utils.ts +299 -0
  61. package/src/crud/lib/crud-validator.ts +247 -0
  62. package/src/crud/lib/data-loader.ts +234 -0
  63. package/src/crud/lib/field-calculator.ts +241 -0
  64. package/src/crud/lib/field-formatter.ts +240 -0
  65. package/src/crud/lib/import-export-service.test.ts +290 -0
  66. package/src/crud/lib/import-export-service.ts +352 -0
  67. package/src/crud/lib/import-server-utils.ts +109 -0
  68. package/src/crud/lib/lazy-loader.ts +241 -0
  69. package/src/crud/lib/parse-filters.ts +85 -0
  70. package/src/crud/lib/permissions.ts +52 -0
  71. package/src/crud/lib/serialize-config.ts +60 -0
  72. package/src/crud/lib/stream-loader.ts +145 -0
  73. package/src/crud/lib/translate-config.ts +335 -0
  74. package/src/crud/lib/types.ts +11 -0
  75. package/src/crud/pages/entity-crud-page.tsx +144 -0
  76. package/src/crud/server.ts +8 -0
  77. package/src/home/constants.tsx +142 -0
  78. package/src/home/feature-showcase.tsx +171 -0
  79. package/src/home/home-page.tsx +191 -0
  80. package/src/home/hooks/index.ts +1 -0
  81. package/src/home/hooks/useWidgetPreferences.ts +167 -0
  82. package/src/home/index.ts +33 -0
  83. package/src/home/quick-access-dialog.tsx +271 -0
  84. package/src/home/quick-access-menu.tsx +267 -0
  85. package/src/home/types.ts +140 -0
  86. package/src/home/welcome-card.tsx +92 -0
  87. package/src/home/widget-container.tsx +258 -0
  88. package/src/home/widgets/base-widget.tsx +200 -0
  89. package/src/home/widgets/customers-widget.tsx +74 -0
  90. package/src/home/widgets/index.ts +6 -0
  91. package/src/home/widgets/orders-widget.tsx +87 -0
  92. package/src/home/widgets/revenue-widget.tsx +71 -0
  93. package/src/home/widgets/stock-widget.tsx +109 -0
  94. package/src/hooks/index.tsx +598 -0
  95. package/src/hooks/use-tenant.test.tsx +30 -0
  96. package/src/hooks/use-tenant.ts +5 -0
  97. package/src/index.ts +17 -0
  98. package/src/infrastructure/__tests__/architecture-verification.spec.ts +103 -0
  99. package/src/infrastructure/api-service.ts +317 -0
  100. package/src/infrastructure/cache/cache-manager.ts +107 -0
  101. package/src/infrastructure/cache/cache.ts +120 -0
  102. package/src/infrastructure/cache/index.ts +8 -0
  103. package/src/infrastructure/cache/types.ts +48 -0
  104. package/src/infrastructure/cron/cron-manager.ts +239 -0
  105. package/src/infrastructure/cron/index.ts +6 -0
  106. package/src/infrastructure/cron/types.ts +41 -0
  107. package/src/infrastructure/event-bus/event-bus.ts +145 -0
  108. package/src/infrastructure/event-bus/index.ts +2 -0
  109. package/src/infrastructure/event-bus/types.ts +22 -0
  110. package/src/infrastructure/index.ts +32 -0
  111. package/src/infrastructure/lock/decorators.ts +67 -0
  112. package/src/infrastructure/lock/index.ts +2 -0
  113. package/src/infrastructure/lock/lock-manager.ts +33 -0
  114. package/src/infrastructure/logger/index.ts +2 -0
  115. package/src/infrastructure/logger/logger.ts +96 -0
  116. package/src/infrastructure/logger/types.ts +25 -0
  117. package/src/layout/index.tsx +185 -0
  118. package/src/navigation/index.ts +91 -0
  119. package/src/notification/index.ts +14 -0
  120. package/src/notification/notification-service.ts +120 -0
  121. package/src/notification/storage/in-memory.ts +56 -0
  122. package/src/notification/storage/index.ts +1 -0
  123. package/src/notification/types.ts +51 -0
  124. package/src/organization/branch-service.ts +299 -0
  125. package/src/organization/branches.config.ts +154 -0
  126. package/src/organization/index.ts +5 -0
  127. package/src/plugin/apps-registry.ts +97 -0
  128. package/src/plugin/index.ts +5 -0
  129. package/src/plugin/types.ts +41 -0
  130. package/src/providers/index.tsx +109 -0
  131. package/src/providers/tenant-provider.tsx +45 -0
  132. package/src/rbac/components/roles/role-card.tsx +158 -0
  133. package/src/rbac/components/roles/role-stats-cards.tsx +29 -0
  134. package/src/rbac/components/roles/role-toolbar.tsx +123 -0
  135. package/src/rbac/hooks/use-role-operations.ts +159 -0
  136. package/src/rbac/hooks/use-roles-data.ts +59 -0
  137. package/src/rbac/index.ts +297 -0
  138. package/src/rbac/lib/permission-helpers.ts +63 -0
  139. package/src/rbac/pages/action-list-page.tsx +25 -0
  140. package/src/rbac/pages/resource-list-page.tsx +25 -0
  141. package/src/rbac/pages/role-list-page.tsx +378 -0
  142. package/src/rbac/permission-service.ts +140 -0
  143. package/src/rbac/permissions.ts +135 -0
  144. package/src/rbac/resource-service.ts +115 -0
  145. package/src/rbac/resource-validator.ts +119 -0
  146. package/src/rbac/role-service.ts +165 -0
  147. package/src/rbac/server.ts +16 -0
  148. package/src/rbac/types.ts +38 -0
  149. package/src/schemas/action.schema.ts +66 -0
  150. package/src/schemas/branch.schema.ts +52 -0
  151. package/src/schemas/coming-soon-schema.ts +9 -0
  152. package/src/schemas/company.schema.ts +44 -0
  153. package/src/schemas/forgot-passward-schema.ts +9 -0
  154. package/src/schemas/index.ts +30 -0
  155. package/src/schemas/material-category.schema.ts +43 -0
  156. package/src/schemas/material-pricing.schema.ts +74 -0
  157. package/src/schemas/material.schema.ts +76 -0
  158. package/src/schemas/materials.ts +52 -0
  159. package/src/schemas/new-passward-schema.ts +15 -0
  160. package/src/schemas/partner-company.schema.ts +149 -0
  161. package/src/schemas/register-schema.ts +36 -0
  162. package/src/schemas/resource.schema.ts +133 -0
  163. package/src/schemas/role.schema.ts +11 -0
  164. package/src/schemas/sign-in-schema.ts +24 -0
  165. package/src/schemas/supplier-pricing.schema.ts +15 -0
  166. package/src/schemas/supplier.schema.ts +120 -0
  167. package/src/schemas/system-category-group.schema.ts +67 -0
  168. package/src/schemas/system-category.schema.ts +77 -0
  169. package/src/schemas/system-config.schema.ts +118 -0
  170. package/src/schemas/uom.schema.ts +75 -0
  171. package/src/schemas/user-supplier.schema.ts +179 -0
  172. package/src/schemas/user.schema.ts +18 -0
  173. package/src/schemas/verify-email-schema.ts +9 -0
  174. package/src/schemas/warehouse.schema.ts +49 -0
  175. package/src/system/components/categories/category-list.tsx +529 -0
  176. package/src/system/components/categories/category-manager.tsx +89 -0
  177. package/src/system/components/categories/group-sidebar.tsx +308 -0
  178. package/src/system/components/settings/setting-dialogs.tsx +197 -0
  179. package/src/system/components/settings/setting-field.tsx +291 -0
  180. package/src/system/components/settings/setting-form-dialog.tsx +308 -0
  181. package/src/system/components/settings/settings-groups.ts +80 -0
  182. package/src/system/components/settings/settings-search.tsx +71 -0
  183. package/src/system/components/settings/settings-section.tsx +74 -0
  184. package/src/system/components/settings/settings-sidebar.tsx +81 -0
  185. package/src/system/constants.ts +3 -0
  186. package/src/system/index.ts +150 -0
  187. package/src/system/job-manager.ts +176 -0
  188. package/src/system/pages/components/categories/category-list.tsx +537 -0
  189. package/src/system/pages/components/categories/category-manager.tsx +90 -0
  190. package/src/system/pages/components/categories/group-sidebar.tsx +311 -0
  191. package/src/system/pages/components/settings/sales-rules-settings.tsx +222 -0
  192. package/src/system/pages/components/settings/setting-dialogs.tsx +197 -0
  193. package/src/system/pages/components/settings/setting-field.tsx +292 -0
  194. package/src/system/pages/components/settings/setting-form-dialog.tsx +308 -0
  195. package/src/system/pages/components/settings/settings-groups.ts +87 -0
  196. package/src/system/pages/components/settings/settings-page.tsx +372 -0
  197. package/src/system/pages/components/settings/settings-search.tsx +71 -0
  198. package/src/system/pages/components/settings/settings-section.tsx +74 -0
  199. package/src/system/pages/components/settings/settings-sidebar.tsx +81 -0
  200. package/src/system/pages/components/settings/system-settings.tsx +244 -0
  201. package/src/system/pages/system-category-page.tsx +15 -0
  202. package/src/system/pages/system-settings-page.tsx +380 -0
  203. package/src/system/schemas/system-category-group.schema.ts +46 -0
  204. package/src/system/schemas/system-category.schema.ts +56 -0
  205. package/src/system/services/settings-service.ts +127 -0
  206. package/src/system/services/system-category-service.ts +63 -0
  207. package/src/system/types.ts +45 -0
  208. package/src/types/index.ts +703 -0
  209. package/src/ui/auth/auth-layout.tsx +135 -0
  210. package/src/ui/auth/forgot-password-form.tsx +98 -0
  211. package/src/ui/auth/index.tsx +7 -0
  212. package/src/ui/auth/new-password-form.tsx +107 -0
  213. package/src/ui/auth/oauth-links.tsx +30 -0
  214. package/src/ui/auth/register-form.tsx +202 -0
  215. package/src/ui/auth/sign-in-form.tsx +238 -0
  216. package/src/ui/auth/verify-email-form.tsx +104 -0
  217. package/src/ui/crud/index.tsx +10 -0
  218. package/src/ui/data-display/accordion.tsx +65 -0
  219. package/src/ui/data-display/aspect-ratio.tsx +11 -0
  220. package/src/ui/data-display/avatar.tsx +163 -0
  221. package/src/ui/data-display/bento-grid.tsx +77 -0
  222. package/src/ui/data-display/carousel.tsx +249 -0
  223. package/src/ui/data-display/chart.tsx +363 -0
  224. package/src/ui/data-display/code-block-highlight.tsx +54 -0
  225. package/src/ui/data-display/collapsible.tsx +42 -0
  226. package/src/ui/data-display/compact-stat-bar.tsx +149 -0
  227. package/src/ui/data-display/data-table/data-table-context.tsx +255 -0
  228. package/src/ui/data-display/data-table/data-table-empty-state.tsx +133 -0
  229. package/src/ui/data-display/data-table/data-table-skeleton.tsx +145 -0
  230. package/src/ui/data-display/data-table/data-table-toolbar.tsx +353 -0
  231. package/src/ui/data-display/data-table/data-table.tsx +597 -0
  232. package/src/ui/data-display/data-table/index.ts +44 -0
  233. package/src/ui/data-display/data-table-column-header.tsx +75 -0
  234. package/src/ui/data-display/data-table-pagination.tsx +130 -0
  235. package/src/ui/data-display/data-table-view-options.tsx +59 -0
  236. package/src/ui/data-display/formatted-number-input.tsx +210 -0
  237. package/src/ui/data-display/highlight.tsx +20 -0
  238. package/src/ui/data-display/hover-card.tsx +48 -0
  239. package/src/ui/data-display/index.tsx +50 -0
  240. package/src/ui/data-display/iphone-15-pro.tsx +114 -0
  241. package/src/ui/data-display/kanban/index.ts +4 -0
  242. package/src/ui/data-display/kanban/kanban-board.tsx +192 -0
  243. package/src/ui/data-display/kanban/kanban-column.tsx +74 -0
  244. package/src/ui/data-display/kanban/kanban-item.tsx +50 -0
  245. package/src/ui/data-display/kanban/kanban-types.ts +21 -0
  246. package/src/ui/data-display/kpi-card.tsx +68 -0
  247. package/src/ui/data-display/media-grid.tsx +110 -0
  248. package/src/ui/data-display/safari.tsx +175 -0
  249. package/src/ui/data-display/show-more-text.tsx +55 -0
  250. package/src/ui/data-display/tabs.tsx +68 -0
  251. package/src/ui/data-display/timeline.tsx +256 -0
  252. package/src/ui/feedback/alert.tsx +60 -0
  253. package/src/ui/feedback/context-menu.tsx +245 -0
  254. package/src/ui/feedback/drawer.tsx +132 -0
  255. package/src/ui/feedback/error-dialog.tsx +273 -0
  256. package/src/ui/feedback/index.tsx +183 -0
  257. package/src/ui/feedback/progress.tsx +32 -0
  258. package/src/ui/feedback/sheet.tsx +148 -0
  259. package/src/ui/feedback/sonner.tsx +36 -0
  260. package/src/ui/forms/command.tsx +157 -0
  261. package/src/ui/forms/date-picker.tsx +73 -0
  262. package/src/ui/forms/date-range-picker.tsx +76 -0
  263. package/src/ui/forms/date-time-picker.tsx +109 -0
  264. package/src/ui/forms/editor/editor-menu-bar.tsx +394 -0
  265. package/src/ui/forms/editor/index.tsx +130 -0
  266. package/src/ui/forms/editor/multi-select-example.tsx +1234 -0
  267. package/src/ui/forms/emoji-picker.tsx +109 -0
  268. package/src/ui/forms/file-dropzone.tsx +169 -0
  269. package/src/ui/forms/file-thumbnail.tsx +29 -0
  270. package/src/ui/forms/index.tsx +201 -0
  271. package/src/ui/forms/input-file.tsx +99 -0
  272. package/src/ui/forms/input-group.tsx +46 -0
  273. package/src/ui/forms/input-otp.tsx +81 -0
  274. package/src/ui/forms/input-phone.tsx +172 -0
  275. package/src/ui/forms/input-spin.tsx +116 -0
  276. package/src/ui/forms/input-tags.tsx +219 -0
  277. package/src/ui/forms/input-time.tsx +42 -0
  278. package/src/ui/forms/multi-select.tsx +629 -0
  279. package/src/ui/forms/multiple-date-picker.tsx +74 -0
  280. package/src/ui/forms/radio-group.tsx +42 -0
  281. package/src/ui/forms/rating.tsx +158 -0
  282. package/src/ui/forms/time-picker.tsx +57 -0
  283. package/src/ui/index.tsx +17 -0
  284. package/src/ui/layout/animated-list.tsx +77 -0
  285. package/src/ui/layout/animated-sidebar.tsx +294 -0
  286. package/src/ui/layout/command-menu.tsx +355 -0
  287. package/src/ui/layout/customizer.tsx +324 -0
  288. package/src/ui/layout/footer.tsx +43 -0
  289. package/src/ui/layout/full-screen-toggle.tsx +52 -0
  290. package/src/ui/layout/header-breadcrumb.tsx +77 -0
  291. package/src/ui/layout/horizontal-layout-header.tsx +83 -0
  292. package/src/ui/layout/horizontal-layout.tsx +50 -0
  293. package/src/ui/layout/index.tsx +25 -0
  294. package/src/ui/layout/language-dropdown.tsx +103 -0
  295. package/src/ui/layout/logo.tsx +63 -0
  296. package/src/ui/layout/main-layout.tsx +57 -0
  297. package/src/ui/layout/mode-dropdown.tsx +58 -0
  298. package/src/ui/layout/notification-dropdown.tsx +127 -0
  299. package/src/ui/layout/page-tabs.tsx +306 -0
  300. package/src/ui/layout/route-cache.tsx +214 -0
  301. package/src/ui/layout/sidebar-group-icon-menu.tsx +195 -0
  302. package/src/ui/layout/sidebar.tsx +279 -0
  303. package/src/ui/layout/tab-content-cache.tsx +201 -0
  304. package/src/ui/layout/tab-navigation-provider.tsx +536 -0
  305. package/src/ui/layout/toggle-mobile-sidebar.tsx +33 -0
  306. package/src/ui/layout/top-bar-header-menubar.tsx +412 -0
  307. package/src/ui/layout/user-dropdown.tsx +188 -0
  308. package/src/ui/layout/vertical-layout-header.tsx +65 -0
  309. package/src/ui/layout/vertical-layout.tsx +47 -0
  310. package/src/ui/management/audit-log-page.tsx +209 -0
  311. package/src/ui/management/cache-management.tsx +349 -0
  312. package/src/ui/management/index.ts +3 -0
  313. package/src/ui/management/job-management.tsx +308 -0
  314. package/src/ui/pages/not-found.tsx +30 -0
  315. package/src/ui/primitives/badge.tsx +66 -0
  316. package/src/ui/primitives/breadcrumb.tsx +103 -0
  317. package/src/ui/primitives/button.tsx +129 -0
  318. package/src/ui/primitives/calendar.tsx +74 -0
  319. package/src/ui/primitives/card.tsx +86 -0
  320. package/src/ui/primitives/checkbox.tsx +31 -0
  321. package/src/ui/primitives/client.ts +30 -0
  322. package/src/ui/primitives/combobox.tsx +290 -0
  323. package/src/ui/primitives/dialog.tsx +121 -0
  324. package/src/ui/primitives/dropdown-menu.tsx +239 -0
  325. package/src/ui/primitives/dynamic-icon.tsx +24 -0
  326. package/src/ui/primitives/index.tsx +134 -0
  327. package/src/ui/primitives/input-number.tsx +131 -0
  328. package/src/ui/primitives/input.tsx +22 -0
  329. package/src/ui/primitives/keyboard.tsx +23 -0
  330. package/src/ui/primitives/label.tsx +24 -0
  331. package/src/ui/primitives/menubar.tsx +262 -0
  332. package/src/ui/primitives/navigation-menu.tsx +157 -0
  333. package/src/ui/primitives/pagination.tsx +118 -0
  334. package/src/ui/primitives/popover.tsx +56 -0
  335. package/src/ui/primitives/prefetch-link.tsx +60 -0
  336. package/src/ui/primitives/resizable.tsx +59 -0
  337. package/src/ui/primitives/scroll-area.tsx +63 -0
  338. package/src/ui/primitives/select.tsx +172 -0
  339. package/src/ui/primitives/separator.tsx +51 -0
  340. package/src/ui/primitives/sidebar.tsx +844 -0
  341. package/src/ui/primitives/slider.tsx +27 -0
  342. package/src/ui/primitives/status-badge.tsx +47 -0
  343. package/src/ui/primitives/sticky-layout.tsx +50 -0
  344. package/src/ui/primitives/switch.tsx +29 -0
  345. package/src/ui/primitives/table.tsx +116 -0
  346. package/src/ui/primitives/tabs.tsx +55 -0
  347. package/src/ui/primitives/toggle-group.tsx +70 -0
  348. package/src/ui/primitives/toggle.tsx +47 -0
  349. package/src/ui/primitives/tooltip.tsx +59 -0
  350. package/src/user/components/dangerous-zone.tsx +34 -0
  351. package/src/user/components/delete-account-form.tsx +40 -0
  352. package/src/user/components/index.ts +4 -0
  353. package/src/user/components/profile-info-form.tsx +390 -0
  354. package/src/user/components/profile-info.tsx +32 -0
  355. package/src/user/components/unified-profile-dialog.tsx +1019 -0
  356. package/src/user/components/user-stats.tsx +27 -0
  357. package/src/user/components/user-toolbar.tsx +137 -0
  358. package/src/user/components/users-card-view.tsx +253 -0
  359. package/src/user/index.ts +11 -0
  360. package/src/user/pages/user-list-page.tsx +234 -0
  361. package/src/user/pages/users-client-page.tsx +385 -0
  362. package/src/user/profile-page.tsx +19 -0
  363. package/src/user/schemas.ts +68 -0
  364. package/src/user/types.ts +34 -0
  365. package/src/user/user-service.ts +538 -0
  366. package/src/utils/index.ts +906 -0
  367. package/src/workflow/activity-timeline.tsx +412 -0
  368. package/src/workflow/approval-workflow.tsx +31 -0
  369. package/src/workflow/index.ts +2 -0
@@ -0,0 +1,109 @@
1
+ import type { EntityConfig, FieldConfig, ImportResult } from "../../types";
2
+
3
+ export function getRowValue(
4
+ row: Record<string, unknown>,
5
+ field: Pick<FieldConfig, "name" | "label">,
6
+ ): unknown {
7
+ if (Object.prototype.hasOwnProperty.call(row, field.name)) {
8
+ return row[field.name];
9
+ }
10
+ if (Object.prototype.hasOwnProperty.call(row, field.label)) {
11
+ return row[field.label];
12
+ }
13
+ return undefined;
14
+ }
15
+
16
+ export function normalizeRowForConfig(
17
+ row: Record<string, unknown>,
18
+ config: EntityConfig,
19
+ ): Record<string, unknown> {
20
+ const normalized: Record<string, unknown> = { ...row };
21
+ config.fields.forEach((field) => {
22
+ normalized[field.name] = getRowValue(row, field);
23
+ });
24
+ return normalized;
25
+ }
26
+
27
+ function coerceBoolean(value: unknown): boolean | undefined {
28
+ if (value === undefined || value === null || value === "") return undefined;
29
+ if (typeof value === "boolean") return value;
30
+ const v = String(value).trim().toLowerCase();
31
+ if (["true", "1", "yes", "y", "on"].includes(v)) return true;
32
+ if (["false", "0", "no", "n", "off"].includes(v)) return false;
33
+ return undefined;
34
+ }
35
+
36
+ function coerceNumber(value: unknown): number | undefined {
37
+ if (value === undefined || value === null || value === "") return undefined;
38
+ const n = typeof value === "number" ? value : Number(String(value).trim());
39
+ if (Number.isNaN(n)) return undefined;
40
+ return n;
41
+ }
42
+
43
+ function coerceDate(value: unknown): Date | undefined {
44
+ if (value === undefined || value === null || value === "") return undefined;
45
+ if (value instanceof Date) return value;
46
+ const d = new Date(String(value));
47
+ if (Number.isNaN(d.getTime())) return undefined;
48
+ return d;
49
+ }
50
+
51
+ function coerceString(value: unknown): string | undefined {
52
+ if (value === undefined || value === null) return undefined;
53
+ const s = String(value);
54
+ return s === "" ? undefined : s;
55
+ }
56
+
57
+ function coerceStringArray(value: unknown): string[] | undefined {
58
+ if (value === undefined || value === null || value === "") return undefined;
59
+ if (Array.isArray(value)) return value.map((v) => String(v)).filter(Boolean);
60
+ return String(value)
61
+ .split(",")
62
+ .map((v) => v.trim())
63
+ .filter(Boolean);
64
+ }
65
+
66
+ export function coerceRowForConfig(
67
+ row: Record<string, unknown>,
68
+ config: EntityConfig,
69
+ ): Record<string, unknown> {
70
+ const normalized = normalizeRowForConfig(row, config);
71
+ const out: Record<string, unknown> = {};
72
+
73
+ config.fields.forEach((field) => {
74
+ if (field.hideInForm) return;
75
+
76
+ const raw = normalized[field.name];
77
+
78
+ switch (field.type) {
79
+ case "number":
80
+ case "integer":
81
+ out[field.name] = coerceNumber(raw);
82
+ break;
83
+ case "boolean":
84
+ out[field.name] = coerceBoolean(raw);
85
+ break;
86
+ case "date":
87
+ case "datetime":
88
+ out[field.name] = coerceDate(raw);
89
+ break;
90
+ case "multiselect":
91
+ out[field.name] = coerceStringArray(raw);
92
+ break;
93
+ default:
94
+ out[field.name] = coerceString(raw);
95
+ break;
96
+ }
97
+ });
98
+
99
+ return out;
100
+ }
101
+
102
+ export function emptyImportResult(): ImportResult {
103
+ return {
104
+ success: true,
105
+ imported: 0,
106
+ failed: 0,
107
+ errors: [],
108
+ };
109
+ }
@@ -0,0 +1,241 @@
1
+ import type { CrudQueryParams, CrudResponse } from "../../types";
2
+
3
+ import { crudConfig } from "../../configs";
4
+
5
+ import { crudService } from "./crud-service";
6
+
7
+ interface LazyLoadOptions {
8
+ endpoint: string;
9
+ initialPageSize?: number;
10
+ loadMoreSize?: number;
11
+ }
12
+
13
+ interface LazyLoadState {
14
+ data: unknown[];
15
+ total: number;
16
+ loadedPages: Set<number>;
17
+ hasMore: boolean;
18
+ loading: boolean;
19
+ }
20
+
21
+ export class LazyLoader {
22
+ private state: Map<string, LazyLoadState> = new Map();
23
+ private lastAccessTime: Map<string, number> = new Map();
24
+ // Configure via CRUD_LAZY_LOADER_MAX_STATES env variable (default: 50)
25
+ private readonly MAX_STATES = crudConfig.lazyLoader.maxStates;
26
+ // Configure via CRUD_LAZY_LOADER_MAX_AGE_MS env variable (default: 600000 = 10 minutes)
27
+ private readonly MAX_AGE = crudConfig.lazyLoader.maxAge;
28
+ private cleanupInterval: NodeJS.Timeout | null = null;
29
+
30
+ constructor() {
31
+ // Start cleanup interval if in browser environment
32
+ if (typeof window !== "undefined") {
33
+ this.startCleanupInterval();
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Start periodic cleanup of old states
39
+ */
40
+ private startCleanupInterval(): void {
41
+ if (this.cleanupInterval) return;
42
+
43
+ this.cleanupInterval = setInterval(() => {
44
+ this.cleanupOldStates();
45
+ }, 300000); // Run cleanup every 5 minutes
46
+ }
47
+
48
+ /**
49
+ * Stop cleanup interval
50
+ */
51
+ private stopCleanupInterval(): void {
52
+ if (this.cleanupInterval) {
53
+ clearInterval(this.cleanupInterval);
54
+ this.cleanupInterval = null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Cleanup old and unused states
60
+ */
61
+ private cleanupOldStates(): void {
62
+ const now = Date.now();
63
+
64
+ // Remove states older than MAX_AGE
65
+ const keysToDelete: string[] = [];
66
+ this.lastAccessTime.forEach((time, endpoint) => {
67
+ if (now - time > this.MAX_AGE) {
68
+ keysToDelete.push(endpoint);
69
+ }
70
+ });
71
+
72
+ keysToDelete.forEach((endpoint) => {
73
+ this.state.delete(endpoint);
74
+ this.lastAccessTime.delete(endpoint);
75
+ });
76
+
77
+ // If still too many states, remove oldest ones
78
+ if (this.state.size > this.MAX_STATES) {
79
+ const sorted = Array.from(this.lastAccessTime.entries()).sort(
80
+ (a, b) => a[1] - b[1],
81
+ );
82
+
83
+ const toRemove = sorted.slice(0, this.state.size - this.MAX_STATES);
84
+ toRemove.forEach(([endpoint]) => {
85
+ this.state.delete(endpoint);
86
+ this.lastAccessTime.delete(endpoint);
87
+ });
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get or initialize state for an endpoint
93
+ */
94
+ private getState(endpoint: string): LazyLoadState {
95
+ // Track access time for cleanup
96
+ this.lastAccessTime.set(endpoint, Date.now());
97
+
98
+ if (!this.state.has(endpoint)) {
99
+ this.state.set(endpoint, {
100
+ data: [],
101
+ total: 0,
102
+ loadedPages: new Set(),
103
+ hasMore: true,
104
+ loading: false,
105
+ });
106
+ }
107
+ return this.state.get(endpoint)!;
108
+ }
109
+
110
+ /**
111
+ * Load initial data
112
+ */
113
+ async loadInitial(
114
+ endpoint: string,
115
+ queryParams: CrudQueryParams,
116
+ ): Promise<CrudResponse> {
117
+ const state = this.getState(endpoint);
118
+ state.loading = true;
119
+
120
+ try {
121
+ const response = await crudService.fetch(endpoint, {
122
+ ...queryParams,
123
+ page: 1,
124
+ pageSize: queryParams.pageSize,
125
+ });
126
+
127
+ state.data = response.data;
128
+ state.total = response.total;
129
+ state.loadedPages.clear();
130
+ state.loadedPages.add(1);
131
+ state.hasMore = response.data.length < response.total;
132
+
133
+ return response;
134
+ } finally {
135
+ state.loading = false;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Load more data (next page)
141
+ */
142
+ async loadMore(
143
+ endpoint: string,
144
+ queryParams: CrudQueryParams,
145
+ ): Promise<CrudResponse> {
146
+ const state = this.getState(endpoint);
147
+
148
+ if (state.loading || !state.hasMore) {
149
+ return {
150
+ data: [],
151
+ total: state.total,
152
+ page: queryParams.page,
153
+ pageSize: queryParams.pageSize,
154
+ };
155
+ }
156
+
157
+ const nextPage = state.loadedPages.size + 1;
158
+
159
+ // Check if page already loaded
160
+ if (state.loadedPages.has(nextPage)) {
161
+ return {
162
+ data: [],
163
+ total: state.total,
164
+ page: nextPage,
165
+ pageSize: queryParams.pageSize,
166
+ };
167
+ }
168
+
169
+ state.loading = true;
170
+
171
+ try {
172
+ const response = await crudService.fetch(endpoint, {
173
+ ...queryParams,
174
+ page: nextPage,
175
+ pageSize: queryParams.pageSize,
176
+ });
177
+
178
+ // Merge new data with existing
179
+ state.data = [...state.data, ...response.data];
180
+ state.loadedPages.add(nextPage);
181
+ state.hasMore = state.data.length < state.total;
182
+
183
+ return {
184
+ ...response,
185
+ data: state.data,
186
+ };
187
+ } finally {
188
+ state.loading = false;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Reset state for an endpoint
194
+ */
195
+ reset(endpoint: string): void {
196
+ this.state.delete(endpoint);
197
+ this.lastAccessTime.delete(endpoint);
198
+ }
199
+
200
+ /**
201
+ * Cleanup all states (useful for testing or memory management)
202
+ */
203
+ cleanupAll(): void {
204
+ this.state.clear();
205
+ this.lastAccessTime.clear();
206
+ }
207
+
208
+ /**
209
+ * Get current cache size
210
+ */
211
+ getCacheSize(): number {
212
+ return this.state.size;
213
+ }
214
+
215
+ /**
216
+ * Check if has more data to load
217
+ */
218
+ hasMore(endpoint: string): boolean {
219
+ const state = this.state.get(endpoint);
220
+ return state?.hasMore ?? false;
221
+ }
222
+
223
+ /**
224
+ * Get current data
225
+ */
226
+ getData(endpoint: string): unknown[] {
227
+ const state = this.state.get(endpoint);
228
+ return state?.data ?? [];
229
+ }
230
+
231
+ /**
232
+ * Get loading state
233
+ */
234
+ isLoading(endpoint: string): boolean {
235
+ const state = this.state.get(endpoint);
236
+ return state?.loading ?? false;
237
+ }
238
+ }
239
+
240
+ // Singleton instance
241
+ export const lazyLoader = new LazyLoader();
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Parse filters from CRUD query params
3
+ * Extracts filter values from filters array format
4
+ */
5
+
6
+ interface FilterItem {
7
+ name: string;
8
+ value: unknown;
9
+ operator?: string;
10
+ }
11
+
12
+ /**
13
+ * Parse filters from query params and extract specific filter values
14
+ * @param filtersParam - JSON string of filters array
15
+ * @param filterName - Name of the filter to extract
16
+ * @returns Filter value or undefined
17
+ */
18
+ export function parseFilterValue(
19
+ filtersParam: string | null,
20
+ filterName: string,
21
+ ): string | undefined {
22
+ if (!filtersParam) {
23
+ return undefined;
24
+ }
25
+
26
+ try {
27
+ const filters = JSON.parse(filtersParam) as FilterItem[];
28
+
29
+ const filter = filters.find((f) => f.name === filterName);
30
+ if (!filter) {
31
+ return undefined;
32
+ }
33
+
34
+ // Handle "in" operator - take first value from array
35
+ if (filter.operator === "in" && Array.isArray(filter.value)) {
36
+ return filter.value[0] as string;
37
+ }
38
+
39
+ // Handle single value
40
+ if (typeof filter.value === "string") {
41
+ return filter.value;
42
+ }
43
+
44
+ return undefined;
45
+ } catch (error) {
46
+ console.error(`Error parsing filter ${filterName}:`, error);
47
+ return undefined;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Parse multiple filter values from query params
53
+ * @param filtersParam - JSON string of filters array
54
+ * @param filterNames - Array of filter names to extract
55
+ * @returns Object with filter values
56
+ */
57
+ export function parseFilterValues<T extends string>(
58
+ filtersParam: string | null,
59
+ filterNames: T[],
60
+ ): Partial<Record<T, string>> {
61
+ if (!filtersParam) {
62
+ return {};
63
+ }
64
+
65
+ try {
66
+ const filters = JSON.parse(filtersParam) as FilterItem[];
67
+ const result: Partial<Record<T, string>> = {};
68
+
69
+ for (const filterName of filterNames) {
70
+ const filter = filters.find((f) => f.name === filterName);
71
+ if (filter) {
72
+ if (filter.operator === "in" && Array.isArray(filter.value)) {
73
+ result[filterName] = filter.value[0] as string;
74
+ } else if (typeof filter.value === "string") {
75
+ result[filterName] = filter.value;
76
+ }
77
+ }
78
+ }
79
+
80
+ return result;
81
+ } catch (error) {
82
+ console.error("Error parsing filters:", error);
83
+ return {};
84
+ }
85
+ }
@@ -0,0 +1,52 @@
1
+ import type { CrudPermissions, EntityConfig } from "../../types";
2
+ import type { Session } from "next-auth";
3
+
4
+ import { getCrudPermissionsFromSession } from "../../rbac/permissions";
5
+
6
+ /**
7
+ * Get CRUD permissions for a user based on their session
8
+ * Permissions are read from session (loaded from cache in session callback)
9
+ * Uses memoization to return stable object references with TTL
10
+ */
11
+ export async function getCrudPermissions(
12
+ session: Session | null,
13
+ entity: string,
14
+ ): Promise<CrudPermissions> {
15
+ // Get permissions from session (already loaded in JWT)
16
+ const perms = getCrudPermissionsFromSession(session, entity);
17
+ return {
18
+ create: perms.create,
19
+ read: perms.view,
20
+ update: perms.update,
21
+ delete: perms.delete,
22
+ export: perms.export,
23
+ import: perms.import,
24
+ approve: perms.approve,
25
+ reject: perms.reject,
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Merge config permissions with user permissions
31
+ * User permissions take precedence
32
+ * Config permissions are optional, so undefined means "allow if user has permission"
33
+ */
34
+ export function mergePermissions(
35
+ configPermissions: EntityConfig["permissions"],
36
+ userPermissions: CrudPermissions,
37
+ ): CrudPermissions {
38
+ if (!configPermissions) {
39
+ return userPermissions;
40
+ }
41
+
42
+ return {
43
+ create: (configPermissions.create ?? true) && userPermissions.create,
44
+ read: (configPermissions.read ?? true) && userPermissions.read,
45
+ update: (configPermissions.update ?? true) && userPermissions.update,
46
+ delete: (configPermissions.delete ?? true) && userPermissions.delete,
47
+ export: (configPermissions.export ?? true) && userPermissions.export,
48
+ import: (configPermissions.import ?? true) && userPermissions.import,
49
+ approve: (configPermissions.approve ?? true) && userPermissions.approve,
50
+ reject: (configPermissions.reject ?? true) && userPermissions.reject,
51
+ };
52
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Serialize EntityConfig for passing to Client Components
3
+ * Removes non-serializable React components and functions
4
+ */
5
+
6
+ import type { EntityConfig, FilterConfig } from '../../types'
7
+
8
+ export function serializeConfig(config: EntityConfig): Omit<EntityConfig, 'icon'> {
9
+ const { icon, fields, filters, ...rest } = config
10
+
11
+ if (icon && !rest.iconName) {
12
+ const iconComponent = icon as { displayName?: string }
13
+ if (iconComponent.displayName) {
14
+ rest.iconName = iconComponent.displayName
15
+ }
16
+ }
17
+
18
+ const serializedFields = fields.map((field) => {
19
+ const {
20
+ renderCell: _renderCell,
21
+ renderForm: _renderForm,
22
+ renderInput: _renderInput,
23
+ validation: _validation,
24
+ dataSource,
25
+ ...fieldRest
26
+ } = field
27
+
28
+ if (dataSource?.transform) {
29
+ const { transform: _transform, ...dataSourceRest } = dataSource
30
+ return {
31
+ ...fieldRest,
32
+ dataSource: dataSourceRest,
33
+ }
34
+ }
35
+
36
+ return dataSource ? { ...fieldRest, dataSource } : fieldRest
37
+ })
38
+
39
+ const serializedFilters = filters?.map((filter) => {
40
+ const { renderFilter: _renderFilter, ...filterRest } = filter
41
+ return filterRest as Omit<FilterConfig, 'renderFilter'>
42
+ })
43
+
44
+ const serializedRowActions = rest.rowActions?.map((action) => {
45
+ const {
46
+ handler: _handler,
47
+ transformData: _transformData,
48
+ visibleWhen: _visibleWhen,
49
+ ...actionRest
50
+ } = action
51
+ return actionRest
52
+ })
53
+
54
+ return {
55
+ ...rest,
56
+ fields: serializedFields,
57
+ filters: serializedFilters,
58
+ rowActions: serializedRowActions,
59
+ }
60
+ }
@@ -0,0 +1,145 @@
1
+ import type { CrudQueryParams, CrudResponse } from "../../types";
2
+
3
+ interface StreamChunk {
4
+ type: "data" | "error" | "done";
5
+ data?: unknown[];
6
+ error?: string;
7
+ total?: number;
8
+ }
9
+
10
+ /**
11
+ * Stream data from API using fetch streaming
12
+ */
13
+ export async function* streamData(
14
+ endpoint: string,
15
+ queryParams: CrudQueryParams,
16
+ ): AsyncGenerator<StreamChunk, void, unknown> {
17
+ const queryString = new URLSearchParams({
18
+ page: String(queryParams.page),
19
+ pageSize: String(queryParams.pageSize),
20
+ ...(queryParams.search && { search: queryParams.search }),
21
+ ...(queryParams.sort && {
22
+ sortField: queryParams.sort.field,
23
+ sortDirection: queryParams.sort.direction,
24
+ }),
25
+ ...(queryParams.filters && {
26
+ filters: JSON.stringify(queryParams.filters),
27
+ }),
28
+ }).toString();
29
+
30
+ const url = `${endpoint}?${queryString}`;
31
+
32
+ try {
33
+ const response = await fetch(url, {
34
+ headers: {
35
+ Accept: "application/x-ndjson", // Newline-delimited JSON
36
+ },
37
+ });
38
+
39
+ if (!response.ok) {
40
+ yield {
41
+ type: "error",
42
+ error: `Failed to fetch: ${response.statusText}`,
43
+ };
44
+ return;
45
+ }
46
+
47
+ const reader = response.body?.getReader();
48
+ const decoder = new TextDecoder();
49
+ let buffer = "";
50
+
51
+ if (!reader) {
52
+ yield {
53
+ type: "error",
54
+ error: "No response body",
55
+ };
56
+ return;
57
+ }
58
+
59
+ while (true) {
60
+ const { done, value } = await reader.read();
61
+
62
+ if (done) {
63
+ // Process remaining buffer
64
+ if (buffer.trim()) {
65
+ try {
66
+ const data = JSON.parse(buffer);
67
+ yield {
68
+ type: "data",
69
+ data: Array.isArray(data) ? data : [data],
70
+ };
71
+ } catch {
72
+ // Ignore parse errors for last chunk
73
+ }
74
+ }
75
+ yield { type: "done" };
76
+ break;
77
+ }
78
+
79
+ buffer += decoder.decode(value, { stream: true });
80
+ const lines = buffer.split("\n");
81
+ buffer = lines.pop() || ""; // Keep incomplete line in buffer
82
+
83
+ for (const line of lines) {
84
+ if (line.trim()) {
85
+ try {
86
+ const data = JSON.parse(line);
87
+ yield {
88
+ type: "data",
89
+ data: Array.isArray(data) ? data : [data],
90
+ total: data.total,
91
+ };
92
+ } catch (error) {
93
+ yield {
94
+ type: "error",
95
+ error: `Parse error: ${error instanceof Error ? error.message : "Unknown error"}`,
96
+ };
97
+ }
98
+ }
99
+ }
100
+ }
101
+ } catch (error) {
102
+ yield {
103
+ type: "error",
104
+ error: error instanceof Error ? error.message : "Stream failed",
105
+ };
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Load data progressively from stream
111
+ */
112
+ export async function loadStreamData(
113
+ endpoint: string,
114
+ queryParams: CrudQueryParams,
115
+ onChunk: (chunk: unknown[]) => void,
116
+ ): Promise<CrudResponse> {
117
+ const allData: unknown[] = [];
118
+ let total = 0;
119
+
120
+ try {
121
+ for await (const chunk of streamData(endpoint, queryParams)) {
122
+ if (chunk.type === "data" && chunk.data) {
123
+ allData.push(...chunk.data);
124
+ if (chunk.total) {
125
+ total = chunk.total;
126
+ }
127
+ onChunk(chunk.data);
128
+ } else if (chunk.type === "error") {
129
+ throw new Error(chunk.error);
130
+ } else if (chunk.type === "done") {
131
+ break;
132
+ }
133
+ }
134
+
135
+ return {
136
+ data: allData,
137
+ total,
138
+ page: queryParams.page,
139
+ pageSize: queryParams.pageSize,
140
+ };
141
+ } catch (error) {
142
+ console.error("Stream loading error:", error);
143
+ throw error;
144
+ }
145
+ }