@goplusvn/core 0.1.0 → 0.1.2

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 (591) hide show
  1. package/package.json +31 -175
  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
  370. package/dist/audit/index.d.mts +0 -115
  371. package/dist/audit/index.d.ts +0 -115
  372. package/dist/audit/index.js +0 -204
  373. package/dist/audit/index.js.map +0 -1
  374. package/dist/audit/index.mjs +0 -200
  375. package/dist/audit/index.mjs.map +0 -1
  376. package/dist/auth/index.d.mts +0 -86
  377. package/dist/auth/index.d.ts +0 -86
  378. package/dist/auth/index.js +0 -210
  379. package/dist/auth/index.js.map +0 -1
  380. package/dist/auth/index.mjs +0 -198
  381. package/dist/auth/index.mjs.map +0 -1
  382. package/dist/button-1dWvP9Ib.d.mts +0 -30
  383. package/dist/button-1dWvP9Ib.d.ts +0 -30
  384. package/dist/calendar-2QzdEo1z.d.mts +0 -20
  385. package/dist/calendar-2QzdEo1z.d.ts +0 -20
  386. package/dist/code-generation/index.d.mts +0 -30
  387. package/dist/code-generation/index.d.ts +0 -30
  388. package/dist/code-generation/index.js +0 -31
  389. package/dist/code-generation/index.js.map +0 -1
  390. package/dist/code-generation/index.mjs +0 -28
  391. package/dist/code-generation/index.mjs.map +0 -1
  392. package/dist/configs/index.d.mts +0 -175
  393. package/dist/configs/index.d.ts +0 -175
  394. package/dist/configs/index.js +0 -254
  395. package/dist/configs/index.js.map +0 -1
  396. package/dist/configs/index.mjs +0 -233
  397. package/dist/configs/index.mjs.map +0 -1
  398. package/dist/crud/index.d.mts +0 -646
  399. package/dist/crud/index.d.ts +0 -646
  400. package/dist/crud/index.js +0 -11772
  401. package/dist/crud/index.js.map +0 -1
  402. package/dist/crud/index.mjs +0 -11665
  403. package/dist/crud/index.mjs.map +0 -1
  404. package/dist/crud/server.d.mts +0 -20
  405. package/dist/crud/server.d.ts +0 -20
  406. package/dist/crud/server.js +0 -123
  407. package/dist/crud/server.js.map +0 -1
  408. package/dist/crud/server.mjs +0 -120
  409. package/dist/crud/server.mjs.map +0 -1
  410. package/dist/data-table-skeleton-12NA8Mjx.d.mts +0 -39
  411. package/dist/data-table-skeleton-12NA8Mjx.d.ts +0 -39
  412. package/dist/dialog-bKfjZMTd.d.mts +0 -22
  413. package/dist/dialog-bKfjZMTd.d.ts +0 -22
  414. package/dist/dynamic-icon-DrGIiu2N.d.mts +0 -10
  415. package/dist/dynamic-icon-DrGIiu2N.d.ts +0 -10
  416. package/dist/home/index.d.mts +0 -269
  417. package/dist/home/index.d.ts +0 -269
  418. package/dist/home/index.js +0 -1678
  419. package/dist/home/index.js.map +0 -1
  420. package/dist/home/index.mjs +0 -1635
  421. package/dist/home/index.mjs.map +0 -1
  422. package/dist/hooks/index.d.mts +0 -7
  423. package/dist/hooks/index.d.ts +0 -7
  424. package/dist/hooks/index.js +0 -8316
  425. package/dist/hooks/index.js.map +0 -1
  426. package/dist/hooks/index.mjs +0 -8255
  427. package/dist/hooks/index.mjs.map +0 -1
  428. package/dist/index-50hpiPrV.d.ts +0 -116
  429. package/dist/index-B9zQVEVi.d.mts +0 -116
  430. package/dist/index.d.mts +0 -5
  431. package/dist/index.d.ts +0 -5
  432. package/dist/index.js +0 -123
  433. package/dist/index.js.map +0 -1
  434. package/dist/index.mjs +0 -118
  435. package/dist/index.mjs.map +0 -1
  436. package/dist/infrastructure/index.d.mts +0 -423
  437. package/dist/infrastructure/index.d.ts +0 -423
  438. package/dist/infrastructure/index.js +0 -633
  439. package/dist/infrastructure/index.js.map +0 -1
  440. package/dist/infrastructure/index.mjs +0 -619
  441. package/dist/infrastructure/index.mjs.map +0 -1
  442. package/dist/label-DWTEkNPo.d.ts +0 -226
  443. package/dist/label-LPpdcoBx.d.mts +0 -226
  444. package/dist/layout/index.d.mts +0 -48
  445. package/dist/layout/index.d.ts +0 -48
  446. package/dist/layout/index.js +0 -117
  447. package/dist/layout/index.js.map +0 -1
  448. package/dist/layout/index.mjs +0 -90
  449. package/dist/layout/index.mjs.map +0 -1
  450. package/dist/navigation/index.d.mts +0 -16
  451. package/dist/navigation/index.d.ts +0 -16
  452. package/dist/navigation/index.js +0 -53
  453. package/dist/navigation/index.js.map +0 -1
  454. package/dist/navigation/index.mjs +0 -50
  455. package/dist/navigation/index.mjs.map +0 -1
  456. package/dist/notification/index.d.mts +0 -105
  457. package/dist/notification/index.d.ts +0 -105
  458. package/dist/notification/index.js +0 -278
  459. package/dist/notification/index.js.map +0 -1
  460. package/dist/notification/index.mjs +0 -274
  461. package/dist/notification/index.mjs.map +0 -1
  462. package/dist/organization/index.d.mts +0 -99
  463. package/dist/organization/index.d.ts +0 -99
  464. package/dist/organization/index.js +0 -360
  465. package/dist/organization/index.js.map +0 -1
  466. package/dist/organization/index.mjs +0 -352
  467. package/dist/organization/index.mjs.map +0 -1
  468. package/dist/plugin/index.d.mts +0 -83
  469. package/dist/plugin/index.d.ts +0 -83
  470. package/dist/plugin/index.js +0 -86
  471. package/dist/plugin/index.js.map +0 -1
  472. package/dist/plugin/index.mjs +0 -84
  473. package/dist/plugin/index.mjs.map +0 -1
  474. package/dist/providers/index.d.mts +0 -25
  475. package/dist/providers/index.d.ts +0 -25
  476. package/dist/providers/index.js +0 -84
  477. package/dist/providers/index.js.map +0 -1
  478. package/dist/providers/index.mjs +0 -77
  479. package/dist/providers/index.mjs.map +0 -1
  480. package/dist/rbac/index.d.mts +0 -226
  481. package/dist/rbac/index.d.ts +0 -226
  482. package/dist/rbac/index.js +0 -4784
  483. package/dist/rbac/index.js.map +0 -1
  484. package/dist/rbac/index.mjs +0 -4722
  485. package/dist/rbac/index.mjs.map +0 -1
  486. package/dist/rbac/permissions.d.mts +0 -26
  487. package/dist/rbac/permissions.d.ts +0 -26
  488. package/dist/rbac/permissions.js +0 -94
  489. package/dist/rbac/permissions.js.map +0 -1
  490. package/dist/rbac/permissions.mjs +0 -90
  491. package/dist/rbac/permissions.mjs.map +0 -1
  492. package/dist/rbac/server.d.mts +0 -1
  493. package/dist/rbac/server.d.ts +0 -1
  494. package/dist/rbac/server.js +0 -128
  495. package/dist/rbac/server.js.map +0 -1
  496. package/dist/rbac/server.mjs +0 -124
  497. package/dist/rbac/server.mjs.map +0 -1
  498. package/dist/schemas/index.d.mts +0 -1257
  499. package/dist/schemas/index.d.ts +0 -1257
  500. package/dist/schemas/index.js +0 -572
  501. package/dist/schemas/index.js.map +0 -1
  502. package/dist/schemas/index.mjs +0 -523
  503. package/dist/schemas/index.mjs.map +0 -1
  504. package/dist/server-QuYCTa89.d.mts +0 -83
  505. package/dist/server-QuYCTa89.d.ts +0 -83
  506. package/dist/sonner-C74GlRDQ.d.mts +0 -71
  507. package/dist/sonner-C74GlRDQ.d.ts +0 -71
  508. package/dist/status-BOXZgIqX.d.mts +0 -12
  509. package/dist/status-BOXZgIqX.d.ts +0 -12
  510. package/dist/system/index.d.mts +0 -77
  511. package/dist/system/index.d.ts +0 -77
  512. package/dist/system/index.js +0 -102
  513. package/dist/system/index.js.map +0 -1
  514. package/dist/system/index.mjs +0 -100
  515. package/dist/system/index.mjs.map +0 -1
  516. package/dist/tabs-C6FfBwPY.d.mts +0 -18
  517. package/dist/tabs-C6FfBwPY.d.ts +0 -18
  518. package/dist/tenant-provider-B8eC_Wpb.d.mts +0 -27
  519. package/dist/tenant-provider-B8eC_Wpb.d.ts +0 -27
  520. package/dist/types/index.d.mts +0 -469
  521. package/dist/types/index.d.ts +0 -469
  522. package/dist/types/index.js +0 -25
  523. package/dist/types/index.js.map +0 -1
  524. package/dist/types/index.mjs +0 -21
  525. package/dist/types/index.mjs.map +0 -1
  526. package/dist/ui/auth.d.mts +0 -39
  527. package/dist/ui/auth.d.ts +0 -39
  528. package/dist/ui/auth.js +0 -4941
  529. package/dist/ui/auth.js.map +0 -1
  530. package/dist/ui/auth.mjs +0 -4896
  531. package/dist/ui/auth.mjs.map +0 -1
  532. package/dist/ui/crud.d.mts +0 -2
  533. package/dist/ui/crud.d.ts +0 -2
  534. package/dist/ui/crud.js +0 -4
  535. package/dist/ui/crud.js.map +0 -1
  536. package/dist/ui/crud.mjs +0 -3
  537. package/dist/ui/crud.mjs.map +0 -1
  538. package/dist/ui/data-display.d.mts +0 -596
  539. package/dist/ui/data-display.d.ts +0 -596
  540. package/dist/ui/data-display.js +0 -5307
  541. package/dist/ui/data-display.js.map +0 -1
  542. package/dist/ui/data-display.mjs +0 -5212
  543. package/dist/ui/data-display.mjs.map +0 -1
  544. package/dist/ui/feedback.d.mts +0 -55
  545. package/dist/ui/feedback.d.ts +0 -55
  546. package/dist/ui/feedback.js +0 -2608
  547. package/dist/ui/feedback.js.map +0 -1
  548. package/dist/ui/feedback.mjs +0 -2526
  549. package/dist/ui/feedback.mjs.map +0 -1
  550. package/dist/ui/forms.d.mts +0 -309
  551. package/dist/ui/forms.d.ts +0 -309
  552. package/dist/ui/forms.js +0 -4656
  553. package/dist/ui/forms.js.map +0 -1
  554. package/dist/ui/forms.mjs +0 -4571
  555. package/dist/ui/forms.mjs.map +0 -1
  556. package/dist/ui/index.d.mts +0 -331
  557. package/dist/ui/index.d.ts +0 -331
  558. package/dist/ui/index.js +0 -16953
  559. package/dist/ui/index.js.map +0 -1
  560. package/dist/ui/index.mjs +0 -16598
  561. package/dist/ui/index.mjs.map +0 -1
  562. package/dist/ui/primitives/client.d.mts +0 -61
  563. package/dist/ui/primitives/client.d.ts +0 -61
  564. package/dist/ui/primitives/client.js +0 -3408
  565. package/dist/ui/primitives/client.js.map +0 -1
  566. package/dist/ui/primitives/client.mjs +0 -3256
  567. package/dist/ui/primitives/client.mjs.map +0 -1
  568. package/dist/ui/primitives.d.mts +0 -113
  569. package/dist/ui/primitives.d.ts +0 -113
  570. package/dist/ui/primitives.js +0 -3356
  571. package/dist/ui/primitives.js.map +0 -1
  572. package/dist/ui/primitives.mjs +0 -3227
  573. package/dist/ui/primitives.mjs.map +0 -1
  574. package/dist/user/index.d.mts +0 -228
  575. package/dist/user/index.d.ts +0 -228
  576. package/dist/user/index.js +0 -4306
  577. package/dist/user/index.js.map +0 -1
  578. package/dist/user/index.mjs +0 -4260
  579. package/dist/user/index.mjs.map +0 -1
  580. package/dist/utils/index.d.mts +0 -205
  581. package/dist/utils/index.d.ts +0 -205
  582. package/dist/utils/index.js +0 -574
  583. package/dist/utils/index.js.map +0 -1
  584. package/dist/utils/index.mjs +0 -514
  585. package/dist/utils/index.mjs.map +0 -1
  586. package/dist/workflow/index.d.mts +0 -40
  587. package/dist/workflow/index.d.ts +0 -40
  588. package/dist/workflow/index.js +0 -3710
  589. package/dist/workflow/index.js.map +0 -1
  590. package/dist/workflow/index.mjs +0 -3677
  591. package/dist/workflow/index.mjs.map +0 -1
@@ -0,0 +1,1017 @@
1
+ "use client";
2
+
3
+ import {
4
+ useState,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useCallback,
9
+ Suspense,
10
+ } from "react";
11
+ import dynamic from "next/dynamic";
12
+ import { Card, CardContent, CardHeader } from "../../ui";
13
+
14
+ import { Button } from "../../ui";
15
+ import { globalError } from "../../ui/feedback/error-dialog";
16
+ import { Separator } from "../../ui";
17
+ import { Badge } from "../../ui";
18
+ import { DynamicIcon } from "../../ui";
19
+ import { Plus, Loader2 } from "lucide-react";
20
+ import { toast } from "sonner";
21
+ import type {
22
+ EntityConfig,
23
+ CrudPermissions,
24
+ CrudResponse,
25
+ RowAction,
26
+ } from "../../types";
27
+ import { CrudProvider } from "./crud-provider";
28
+ import { useCrudConfig, useCrudState } from "./crud-context";
29
+ import { CrudTable } from "./crud-table";
30
+ import { CrudTableToolbar } from "./crud-table-toolbar";
31
+ import { useRouter, useParams } from "next/navigation";
32
+ import { CrudBulkActions } from "./crud-bulk-actions";
33
+ import { CrudDeleteDialog } from "./crud-delete-dialog";
34
+ import { crudService } from "../lib/crud-service";
35
+ import { usePrefetch } from "../../hooks";
36
+ import type { DictionaryType } from "../../hooks";
37
+
38
+ // ✅ Defer non-critical features - CardView only loads when needed
39
+ const CrudCardView = dynamic(
40
+ () => import("./crud-card-view").then((m) => ({ default: m.CrudCardView })),
41
+ {
42
+ loading: () => (
43
+ <div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
44
+ {Array.from({ length: 6 }).map((_, index) => (
45
+ <Card key={`skeleton-card-${index}`} className="animate-pulse">
46
+ <CardHeader className="pb-3">
47
+ <div className="flex items-start gap-2">
48
+ <div className="h-4 w-4 bg-muted rounded shrink-0 mt-0.5" />
49
+ <div className="flex-1 space-y-2">
50
+ <div className="h-4 w-3/4 bg-muted rounded" />
51
+ <div className="h-3 w-1/2 bg-muted rounded" />
52
+ </div>
53
+ </div>
54
+ </CardHeader>
55
+ <CardContent className="pt-0">
56
+ <div className="space-y-2">
57
+ <div className="h-4 w-full bg-muted rounded" />
58
+ <div className="h-4 w-2/3 bg-muted rounded" />
59
+ </div>
60
+ </CardContent>
61
+ </Card>
62
+ ))}
63
+ </div>
64
+ ),
65
+ ssr: false,
66
+ },
67
+ );
68
+
69
+ // ✅ Dynamic imports for code splitting - only load when needed
70
+ const CrudDialog = dynamic(
71
+ () => import("./crud-dialog").then((m) => ({ default: m.CrudDialog })),
72
+ {
73
+ loading: () => (
74
+ <div className="flex items-center justify-center p-4">
75
+ <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
76
+ </div>
77
+ ),
78
+ ssr: false, // Dialog doesn't need SSR
79
+ },
80
+ );
81
+
82
+ const CrudSheet = dynamic(
83
+ () => import("./crud-sheet").then((m) => ({ default: m.CrudSheet })),
84
+ {
85
+ loading: () => (
86
+ <div className="flex items-center justify-center p-4">
87
+ <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
88
+ </div>
89
+ ),
90
+ ssr: false, // Sheet doesn't need SSR
91
+ },
92
+ );
93
+
94
+ const CrudImportDialog = dynamic(
95
+ () =>
96
+ import("./crud-import-dialog").then((m) => ({
97
+ default: m.CrudImportDialog,
98
+ })),
99
+ {
100
+ ssr: false, // Import dialog doesn't need SSR
101
+ },
102
+ );
103
+
104
+ const CrudExportButton = dynamic(
105
+ () =>
106
+ import("./crud-export-button").then((m) => ({
107
+ default: m.CrudExportButton,
108
+ })),
109
+ {
110
+ ssr: false, // Export button doesn't need SSR
111
+ },
112
+ );
113
+
114
+ interface CrudPageContentProps {
115
+ config: EntityConfig;
116
+ permissions: CrudPermissions;
117
+ entityName?: string; // Entity name from URL route (e.g., "suppliers", "warehouses")
118
+ dictionary: DictionaryType;
119
+ customActions?: React.ReactNode;
120
+ }
121
+
122
+ function CrudPageContent({
123
+ config,
124
+ permissions,
125
+ entityName,
126
+ dictionary,
127
+ customActions,
128
+ }: CrudPageContentProps) {
129
+
130
+ const router = useRouter();
131
+ const params = useParams();
132
+ const lang = (params?.lang as string) || "vi";
133
+ const { prefetchNextPage } = usePrefetch();
134
+
135
+ // Get CRUD translations from dictionary
136
+ const crudDict = (dictionary as Record<string, unknown>).crud as
137
+ | Record<string, unknown>
138
+ | undefined;
139
+ const commonDict = (crudDict?.common as Record<string, string>) || {};
140
+ const messagesDict = (crudDict?.messages as Record<string, string>) || {};
141
+
142
+ // Create translations object for child components
143
+ const translations = {
144
+ edit: commonDict.edit || "Edit",
145
+ delete: commonDict.delete || "Delete",
146
+ save: commonDict.save || "Save",
147
+ cancel: commonDict.cancel || "Cancel",
148
+ create: commonDict.create || "Create",
149
+ update: commonDict.update || "Update",
150
+ creating: commonDict.creating || "Creating...",
151
+ updating: commonDict.updating || "Updating...",
152
+ deleting: commonDict.deleting || "Deleting...",
153
+ savedSuccessfully: commonDict.savedSuccessfully || "Saved successfully!",
154
+ createdSuccessfully:
155
+ commonDict.createdSuccessfully || "Created successfully!",
156
+ updatedSuccessfully:
157
+ commonDict.updatedSuccessfully || "Updated successfully!",
158
+ confirmDeleteTitle: commonDict.confirmDeleteTitle,
159
+ confirmBulkDeleteTitle: commonDict.confirmBulkDeleteTitle,
160
+ confirmDeleteDescription: commonDict.confirmDeleteDescription,
161
+ confirmBulkDeleteDescription: commonDict.confirmBulkDeleteDescription,
162
+ deleteSelected: commonDict.deleteSelected || "Delete selected",
163
+ actions: commonDict.actions || "Actions",
164
+ add: commonDict.add || "Add",
165
+ addNew: commonDict.addNew || "Add New",
166
+ };
167
+
168
+ // Helper function to get translation with fallback
169
+ // Supports dot notation for nested keys (e.g., "crud.common.options.active")
170
+ const t = useCallback(
171
+ (key: string, fallback: string): string => {
172
+ if (!key) return fallback;
173
+
174
+ // 1. Try simple lookup in common dict first (legacy behavior)
175
+ if (commonDict[key]) return commonDict[key];
176
+
177
+ // 2. Try deep lookup in full dictionary
178
+ const keys = key.split(".");
179
+ let current: any = dictionary;
180
+
181
+ for (const k of keys) {
182
+ if (current === undefined || current === null) break;
183
+ current = current[k];
184
+ }
185
+
186
+ if (current && typeof current === "string") {
187
+ return current;
188
+ }
189
+
190
+ return fallback;
191
+ },
192
+ [commonDict, dictionary],
193
+ );
194
+
195
+ // Helper function to get message translation with parameters
196
+ const tMessage = useCallback(
197
+ (key: string, params?: Record<string, string | number>): string => {
198
+ let message = messagesDict[key] || key;
199
+ if (params) {
200
+ Object.entries(params).forEach(([param, value]) => {
201
+ message = message.replace(
202
+ new RegExp(`\\{\\{${param}\\}\\}`, "g"),
203
+ String(value),
204
+ );
205
+ });
206
+ }
207
+ return message;
208
+ },
209
+ [messagesDict],
210
+ );
211
+
212
+ // Get form modes from config (default to "dialog")
213
+ const createMode = config.formMode?.create || "dialog";
214
+ const editMode = config.formMode?.edit || "dialog";
215
+ const sheetSide = config.formMode?.sheetSide || "right";
216
+
217
+ // Load view mode from localStorage or default to "table"
218
+ // ✅ Fix hydration mismatch: Initialize with default value, sync with localStorage in useEffect
219
+ const [viewMode, setViewMode] = useState<"table" | "card">("table");
220
+
221
+ useEffect(() => {
222
+ if (typeof window !== "undefined") {
223
+ const saved = localStorage.getItem(`crud-view-mode-${config.name}`);
224
+ if (saved === "table" || saved === "card") {
225
+ setViewMode(saved);
226
+ }
227
+ }
228
+ }, [config.name]);
229
+
230
+ const handleViewModeChange = (mode: "table" | "card") => {
231
+ setViewMode(mode);
232
+ if (typeof window !== "undefined") {
233
+ localStorage.setItem(`crud-view-mode-${config.name}`, mode);
234
+ }
235
+ };
236
+
237
+ const { setConfig, setPermissions } = useCrudConfig();
238
+
239
+ const {
240
+ getQueryParams,
241
+ pagination,
242
+ setPage,
243
+ search,
244
+ sorting,
245
+ filters,
246
+ setSearch,
247
+ clearFilters,
248
+ } = useCrudState();
249
+ const [data, setData] = useState<CrudResponse>({
250
+ data: [],
251
+ total: 0,
252
+ page: 1,
253
+ pageSize: 10,
254
+ });
255
+ const [loading, setLoading] = useState(true);
256
+ const [tableLoading, setTableLoading] = useState(false); // Separate loading state for table only
257
+ const [dialogOpen, setDialogOpen] = useState(false);
258
+ const [editingRowId, setEditingRowId] = useState<string | null>(null);
259
+ const [tableInstance, setTableInstance] = useState<any>(null);
260
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
261
+ const [deletingRowId, setDeletingRowId] = useState<string | null>(null);
262
+ const [bulkDeleteIds, setBulkDeleteIds] = useState<string[]>([]);
263
+ const [deleteLoading, setDeleteLoading] = useState(false);
264
+ const currentRequestRef = useRef<number>(0);
265
+ const hasInitializedRef = useRef<boolean>(false);
266
+ const previousQueryParamsRef = useRef<string>("");
267
+ const abortControllerRef = useRef<AbortController | null>(null);
268
+
269
+ // Initialize config and permissions
270
+ useEffect(() => {
271
+ setConfig(config);
272
+ setPermissions(permissions);
273
+ }, [config, permissions, setConfig, setPermissions]);
274
+
275
+ // Memoize filters string to avoid unnecessary re-renders
276
+ const filtersKey = useMemo(() => {
277
+ // Sort a copy to avoid mutating the original array
278
+ const sortedFilters = [...filters].sort((a, b) =>
279
+ a.name.localeCompare(b.name),
280
+ );
281
+ return JSON.stringify(sortedFilters);
282
+ }, [filters]);
283
+
284
+ // Memoize query params string to detect actual changes
285
+ const queryParamsString = useMemo(() => {
286
+ return JSON.stringify({
287
+ page: pagination.page,
288
+ pageSize: pagination.pageSize,
289
+ search: search || "",
290
+ sortField: sorting?.field || "",
291
+ sortDirection: sorting?.direction || "",
292
+ filters: filtersKey, // Use filtersKey which is already a JSON string
293
+ });
294
+ }, [
295
+ pagination.page,
296
+ pagination.pageSize,
297
+ search,
298
+ sorting?.field,
299
+ sorting?.direction,
300
+ filtersKey,
301
+ ]);
302
+
303
+ // Fetch data with race condition protection and cancellation
304
+ const fetchData = useCallback(
305
+ async (isInitialLoad = false) => {
306
+ // Increment request ID to track the latest request
307
+ const requestId = ++currentRequestRef.current;
308
+
309
+ // Cancel previous request if exists
310
+ if (abortControllerRef.current) {
311
+ abortControllerRef.current.abort();
312
+ }
313
+
314
+ // Create new abort controller
315
+ const abortController = new AbortController();
316
+ abortControllerRef.current = abortController;
317
+
318
+ // Use full loading for initial load, table loading for pagination
319
+ if (isInitialLoad) {
320
+ setLoading(true);
321
+ } else {
322
+ setTableLoading(true);
323
+ }
324
+
325
+ try {
326
+ const queryParams = getQueryParams();
327
+ const response = await crudService.fetch(
328
+ config.apiEndpoint,
329
+ queryParams,
330
+ abortController.signal,
331
+ );
332
+
333
+ // Only set data if this is still the latest request
334
+ if (requestId === currentRequestRef.current) {
335
+ setData(response);
336
+ }
337
+ } catch (error) {
338
+ // Ignore abort errors
339
+ if (error instanceof Error && error.name === "AbortError") {
340
+ return;
341
+ }
342
+
343
+ // Only handle error if this is still the latest request
344
+ if (requestId === currentRequestRef.current) {
345
+ console.error("Error fetching data:", error);
346
+ // Set empty data on error
347
+ setData({
348
+ data: [],
349
+ total: 0,
350
+ page: 1,
351
+ pageSize: 10,
352
+ });
353
+ // Show toast notification to user
354
+ // Show error dialog to user
355
+ globalError.emit({
356
+ title: t("error", "Error"),
357
+ description: tMessage("loadFailed", {}),
358
+ trace: error instanceof Error ? error.stack : undefined,
359
+ });
360
+ }
361
+ } finally {
362
+ // Only update loading state if this is still the latest request
363
+ if (requestId === currentRequestRef.current) {
364
+ if (isInitialLoad) {
365
+ setLoading(false);
366
+ } else {
367
+ setTableLoading(false);
368
+ }
369
+ }
370
+ }
371
+ },
372
+ [config.apiEndpoint, getQueryParams, t, tMessage],
373
+ );
374
+
375
+ // Initial load - only run once on mount
376
+ useEffect(() => {
377
+ if (!hasInitializedRef.current) {
378
+ hasInitializedRef.current = true;
379
+ // Initialize previousQueryParamsRef with current query params
380
+ previousQueryParamsRef.current = queryParamsString;
381
+ fetchData(true);
382
+ }
383
+ // eslint-disable-next-line react-hooks/exhaustive-deps
384
+ }, []); // Only run on mount
385
+
386
+ // Listen for external refresh events (e.g., from role dialog)
387
+ useEffect(() => {
388
+ const handleRefresh = () => {
389
+ fetchData(false);
390
+ };
391
+ window.addEventListener("crudRefreshData", handleRefresh);
392
+ return () => {
393
+ window.removeEventListener("crudRefreshData", handleRefresh);
394
+ };
395
+ }, [fetchData]);
396
+
397
+ // ✅ Memoize pageCount to avoid recalculation
398
+ const pageCount = useMemo(() => {
399
+ const total = data.total || 0;
400
+ const pageSize = pagination.pageSize || 10;
401
+ const count = Math.ceil(total / pageSize);
402
+ return count > 0 ? count : 1;
403
+ }, [data.total, pagination.pageSize]);
404
+
405
+ // ✅ Prefetch next page when near the end
406
+ useEffect(() => {
407
+ // Skip prefetch if:
408
+ // - Not initialized yet
409
+ // - Still loading
410
+ // - On last page or beyond
411
+ // - Only one page (no need to prefetch)
412
+ // - On first page (don't prefetch immediately on initial load)
413
+ if (
414
+ !hasInitializedRef.current ||
415
+ loading ||
416
+ tableLoading ||
417
+ pagination.page >= pageCount ||
418
+ pageCount <= 1 ||
419
+ pagination.page === 1 // Don't prefetch on first page load
420
+ ) {
421
+ return;
422
+ }
423
+
424
+ // Prefetch next page when we're within 1 page of the end
425
+ // Only prefetch if there's actually a next page
426
+ if (pagination.page >= pageCount - 1 && pagination.page < pageCount) {
427
+ const queryParams = getQueryParams();
428
+ prefetchNextPage(
429
+ config.apiEndpoint,
430
+ pagination.page,
431
+ pagination.pageSize,
432
+ {
433
+ search: queryParams.search,
434
+ sort: queryParams.sort,
435
+ filters: queryParams.filters,
436
+ },
437
+ );
438
+ }
439
+ }, [
440
+ pagination.page,
441
+ pageCount,
442
+ loading,
443
+ tableLoading,
444
+ config.apiEndpoint,
445
+ pagination.pageSize,
446
+ getQueryParams,
447
+ prefetchNextPage,
448
+ ]);
449
+
450
+ // Fetch data when query params change (except initial load)
451
+ useEffect(() => {
452
+ // Skip if not initialized yet or still initial loading
453
+ if (!hasInitializedRef.current || loading) return;
454
+
455
+ // Only fetch if query params actually changed
456
+ if (queryParamsString !== previousQueryParamsRef.current) {
457
+ previousQueryParamsRef.current = queryParamsString;
458
+ fetchData(false);
459
+ }
460
+ }, [queryParamsString, loading, fetchData]);
461
+
462
+ const handleCreate = () => {
463
+ if (createMode === "navigate") {
464
+ let path =
465
+ config.formMode?.createPath || `/${entityName || config.name}/new`;
466
+ // Add lang prefix if not already present
467
+ if (!path.startsWith(`/${lang}/`)) {
468
+ path = `/${lang}${path}`;
469
+ }
470
+ router.push(path);
471
+ return;
472
+ }
473
+ setEditingRowId(null);
474
+ setDialogOpen(true);
475
+ };
476
+
477
+ const handleEdit = (rowId: string, rowData?: Record<string, unknown>) => {
478
+ if (editMode === "navigate") {
479
+ const editPath =
480
+ config.formMode?.editPath ||
481
+ `/${entityName || config.name}/${rowId}/edit`;
482
+ // For recipes, use dishId from rowData if available, otherwise use rowId
483
+ let idToUse = rowId;
484
+ if (editPath.includes("[dishId]") && rowData?.dishId) {
485
+ idToUse = String(rowData.dishId);
486
+ }
487
+ // Replace both [id] and [dishId] placeholders
488
+ let path = editPath.replace("[id]", rowId).replace("[dishId]", idToUse);
489
+ // Add lang prefix if not already present
490
+ if (!path.startsWith(`/${lang}/`)) {
491
+ path = `/${lang}${path}`;
492
+ }
493
+ router.push(path);
494
+ return;
495
+ }
496
+ setEditingRowId(rowId);
497
+ setDialogOpen(true);
498
+ };
499
+
500
+ const handleDelete = (rowId: string) => {
501
+ setDeletingRowId(rowId);
502
+ setBulkDeleteIds([]);
503
+ setDeleteDialogOpen(true);
504
+ };
505
+
506
+ const handleCustomAction = async (
507
+ action: string,
508
+ rowId: string,
509
+ rowData: Record<string, unknown>,
510
+ ) => {
511
+ const rowAction = config.rowActions?.find((a) => a.action === action);
512
+ if (!rowAction) return;
513
+
514
+ try {
515
+ switch (action) {
516
+ case "copy":
517
+ case "duplicate": {
518
+ // Get data to copy
519
+ let copyData = { ...rowData };
520
+
521
+ // Exclude fields specified in config
522
+ if (rowAction.excludeFields) {
523
+ rowAction.excludeFields.forEach((field) => {
524
+ delete copyData[field];
525
+ });
526
+ } else {
527
+ // Default: exclude id and timestamps
528
+ delete copyData[config.idField];
529
+ delete copyData["id"];
530
+ delete copyData["createdAt"];
531
+ delete copyData["createdBy"];
532
+ delete copyData["updatedAt"];
533
+ delete copyData["updatedBy"];
534
+ }
535
+
536
+ // Transform data if specified
537
+ if (rowAction.transformData) {
538
+ copyData = rowAction.transformData(copyData);
539
+ }
540
+
541
+ // Create new record with copied data
542
+ await crudService.create(config.apiEndpoint, copyData);
543
+ await fetchData(false);
544
+ toast.success(t("success", "Success"), {
545
+ description: tMessage("created", { entity: config.label }),
546
+ });
547
+ break;
548
+ }
549
+
550
+ case "view": {
551
+ // Navigate to view page or open view dialog
552
+ const editPath =
553
+ config.formMode?.editPath ||
554
+ `/${entityName || config.name}/${rowId}`;
555
+ let viewPath = editPath
556
+ .replace("[id]", rowId)
557
+ .replace("[dishId]", rowId);
558
+ // Add lang prefix if not already present
559
+ if (!viewPath.startsWith(`/${lang}/`)) {
560
+ viewPath = `/${lang}${viewPath}`;
561
+ }
562
+ router.push(viewPath);
563
+ break;
564
+ }
565
+
566
+ case "archive": {
567
+ // Archive the record (update status to archived)
568
+ await crudService.update(config.apiEndpoint, rowId, {
569
+ ...rowData,
570
+ status: "archived",
571
+ });
572
+ await fetchData(false);
573
+ toast.success(t("success", "Success"), {
574
+ description: tMessage("updated", { entity: config.label }),
575
+ });
576
+ break;
577
+ }
578
+
579
+ default: {
580
+ // Custom handler: Get original config to access handler function
581
+ // NOTE: This logic is deprecated because Core package cannot import App configs directly.
582
+ // Custom handlers passed via config functions are lost during serialization.
583
+ // If you need custom actions with handlers, please implement them via `customActions` prop
584
+ // or a registered event handler mechanism.
585
+
586
+ console.warn(
587
+ `Action "${action}" has no built-in handler and custom handlers via config are not supported in this architecture. Please use customActions prop.`,
588
+ );
589
+
590
+ /* Deprecated logic removed to fix build:
591
+ if (entityName) {
592
+ try {
593
+ const originalConfig = getEntityConfig(entityName)
594
+ ...
595
+ }
596
+ }
597
+ */
598
+ }
599
+ }
600
+ } catch (error) {
601
+ console.error(`Error executing ${action} action:`, error);
602
+ globalError.emit({
603
+ title: t("error", "Error"),
604
+ description: tMessage("saveFailed", { entity: rowAction.label }),
605
+ trace: error instanceof Error ? error.stack : undefined,
606
+ });
607
+ }
608
+ };
609
+
610
+ const confirmDelete = async () => {
611
+ const rowId = deletingRowId;
612
+ const ids = bulkDeleteIds.length > 0 ? bulkDeleteIds : rowId ? [rowId] : [];
613
+
614
+ if (ids.length === 0) return;
615
+
616
+ setDeleteLoading(true);
617
+ try {
618
+ await Promise.all(
619
+ ids.map((id) => crudService.delete(config.apiEndpoint, id)),
620
+ );
621
+ await fetchData(false); // Only reload table data, not entire component
622
+ toast.success(t("success", "Success"), {
623
+ description:
624
+ ids.length === 1
625
+ ? tMessage("deleted", { entity: config.label })
626
+ : tMessage("bulkDeleted", {
627
+ count: ids.length,
628
+ entities: config.pluralLabel,
629
+ }),
630
+ });
631
+ setDeleteDialogOpen(false);
632
+ setDeletingRowId(null);
633
+ setBulkDeleteIds([]);
634
+ } catch (error) {
635
+ console.error("Error deleting:", error);
636
+
637
+ let description = tMessage("deleteFailed", {
638
+ entity: config.pluralLabel,
639
+ });
640
+
641
+ if (error instanceof Error) {
642
+ description = error.message;
643
+
644
+ // Enhance FK errors for better user experience
645
+ if (
646
+ description.includes("violates foreign key constraint") ||
647
+ description.includes("violates RESTRICT setting")
648
+ ) {
649
+ const match = description.match(/on table "([^"]+)"/);
650
+ if (match) {
651
+ const tableName = match[1];
652
+ // Format table name: role_permissions -> Role Permissions
653
+ const readableTable = tableName
654
+ .split("_")
655
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
656
+ .join(" ");
657
+ description = `Cannot delete because it is used in "${readableTable}".`;
658
+ }
659
+ }
660
+ }
661
+
662
+ globalError.emit({
663
+ title: t("error", "Error"),
664
+ description: description,
665
+ trace: error instanceof Error ? error.stack : undefined,
666
+ });
667
+ } finally {
668
+ setDeleteLoading(false);
669
+ }
670
+ };
671
+
672
+ const handleBulkDelete = (rowIds: string[]) => {
673
+ setDeletingRowId(null);
674
+ setBulkDeleteIds(rowIds);
675
+ setDeleteDialogOpen(true);
676
+ };
677
+
678
+ const handleSubmit = async (formData: Record<string, unknown>) => {
679
+ try {
680
+ if (editingRowId) {
681
+ await crudService.update(config.apiEndpoint, editingRowId, formData);
682
+ toast.success(t("success", "Success"), {
683
+ description: tMessage("updated", { entity: config.label }),
684
+ });
685
+ } else {
686
+ await crudService.create(config.apiEndpoint, formData);
687
+ toast.success(t("success", "Success"), {
688
+ description: tMessage("created", { entity: config.label }),
689
+ });
690
+ }
691
+ await fetchData(false); // Only reload table data, not entire component
692
+ } catch (error) {
693
+ console.error("Error saving:", error);
694
+ // Extract error message from error object
695
+ const errorMessage =
696
+ error instanceof Error
697
+ ? error.message
698
+ : tMessage("saveFailed", { entity: config.label });
699
+
700
+ // Use detailed error message if available, otherwise fallback to generic message
701
+ const displayMessage =
702
+ errorMessage && errorMessage !== "Failed to POST: Bad Request"
703
+ ? errorMessage
704
+ : tMessage("saveFailed", { entity: config.label });
705
+
706
+ globalError.emit({
707
+ title: t("error", "Error"),
708
+ description: displayMessage,
709
+ trace: error instanceof Error ? error.stack : undefined,
710
+ });
711
+ throw error;
712
+ }
713
+ };
714
+
715
+ // ✅ Memoize editingData to avoid recalculation
716
+ const editingData = useMemo(() => {
717
+ if (!editingRowId) return undefined;
718
+ return data.data.find(
719
+ (row) =>
720
+ String((row as Record<string, unknown>)[config.idField]) ===
721
+ editingRowId,
722
+ );
723
+ }, [editingRowId, data.data, config.idField]);
724
+
725
+ // ✅ Memoize deletingData to avoid recalculation
726
+ const deletingData = useMemo(() => {
727
+ if (!deletingRowId) return undefined;
728
+ return data.data.find(
729
+ (row) =>
730
+ String((row as Record<string, unknown>)[config.idField]) ===
731
+ deletingRowId,
732
+ );
733
+ }, [deletingRowId, data.data, config.idField]);
734
+
735
+ return (
736
+ <>
737
+ <div className="space-y-3">
738
+ <div className="pb-2">
739
+ {/* Header: Title + Description + Actions */}
740
+ <div className="flex items-start justify-between gap-4 flex-wrap">
741
+ <div className="space-y-1">
742
+ <div className="flex items-center gap-2 sm:gap-3 flex-wrap">
743
+ {config.iconName && (
744
+ <div className="p-1.5 sm:p-2 rounded-lg bg-primary/10 shrink-0">
745
+ <DynamicIcon
746
+ name={config.iconName as any}
747
+ className="h-4 w-4 sm:h-5 sm:w-5 text-primary"
748
+ />
749
+ </div>
750
+ )}
751
+ <div className="min-w-0 flex-1">
752
+ <h2 className="text-lg sm:text-xl font-bold tracking-tight">
753
+ {config.pluralLabel}
754
+ </h2>
755
+ {config.description && (
756
+ <p className="text-xs sm:text-sm text-muted-foreground mt-0.5 sm:mt-1 hidden sm:block">
757
+ {config.description}
758
+ </p>
759
+ )}
760
+ </div>
761
+ {data.total !== undefined && (
762
+ <Badge
763
+ variant="secondary"
764
+ className="text-xs shrink-0 hidden sm:inline-flex"
765
+ >
766
+ {data.total}{" "}
767
+ {data.total === 1 ? t("item", "item") : t("items", "items")}
768
+ </Badge>
769
+ )}
770
+ </div>
771
+ {/* Mobile: Show total count below title */}
772
+ {data.total !== undefined && (
773
+ <Badge variant="secondary" className="text-xs w-fit sm:hidden">
774
+ {data.total}{" "}
775
+ {data.total === 1 ? t("item", "item") : t("items", "items")}
776
+ </Badge>
777
+ )}
778
+ </div>
779
+
780
+ {/* Action Buttons Grouped */}
781
+ <div className="flex items-center gap-2 flex-wrap sm:flex-nowrap">
782
+ {/* Primary Actions */}
783
+ <div className="flex items-center gap-2 flex-wrap sm:flex-nowrap">
784
+ <CrudBulkActions
785
+ config={config}
786
+ permissions={permissions}
787
+ onDelete={handleBulkDelete}
788
+ />
789
+ {customActions}
790
+ {permissions.create && (
791
+ <Button onClick={handleCreate} size="sm" className="shrink-0">
792
+ <Plus className="mr-2 h-4 w-4" />
793
+ <span className="hidden sm:inline">
794
+ {t("create", "Create")} {config.label}
795
+ </span>
796
+ <span className="sm:hidden">{t("create", "Create")}</span>
797
+ </Button>
798
+ )}
799
+ </div>
800
+
801
+ {/* Secondary Actions - Conditional loading with Suspense */}
802
+ {(permissions.import && config.features?.import) ||
803
+ (permissions.export && config.features?.export) ? (
804
+ <>
805
+ <Separator
806
+ orientation="vertical"
807
+ className="h-6 hidden sm:block"
808
+ />
809
+ <div className="flex items-center gap-2 flex-wrap sm:flex-nowrap">
810
+ {permissions.import && config.features?.import && (
811
+ <Suspense fallback={null}>
812
+ <CrudImportDialog
813
+ config={config}
814
+ endpoint={(() => {
815
+ // Handle endpoint with query params correctly
816
+ const baseUrl = config.apiEndpoint.split("?")[0];
817
+ const queryParams = config.apiEndpoint.includes("?")
818
+ ? config.apiEndpoint.split("?")[1]
819
+ : "";
820
+ return `${baseUrl}/import${queryParams ? `?${queryParams}` : ""}`;
821
+ })()}
822
+ canImport={permissions.import}
823
+ />
824
+ </Suspense>
825
+ )}
826
+ {permissions.export && config.features?.export && (
827
+ <Suspense fallback={null}>
828
+ <CrudExportButton
829
+ endpoint={`/api/crud/${entityName || config.name}/export`}
830
+ filters={filters}
831
+ search={search}
832
+ canExport={permissions.export}
833
+ />
834
+ </Suspense>
835
+ )}
836
+ </div>
837
+ </>
838
+ ) : null}
839
+ </div>
840
+ </div>
841
+ </div>
842
+
843
+ <div className="space-y-3">
844
+ {/* Toolbar Section - Sticky when scrolling */}
845
+ <div className="sticky top-0 z-20 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 pb-2 pt-0 border-b border-border/50 transition-all duration-200">
846
+ <CrudTableToolbar
847
+ table={tableInstance}
848
+ config={config}
849
+ permissions={permissions}
850
+ viewMode={viewMode}
851
+ onViewModeChange={handleViewModeChange}
852
+ />
853
+ </div>
854
+
855
+ {/* Table/Card Section */}
856
+ <div className="overflow-hidden">
857
+ {viewMode === "table" ? (
858
+ <CrudTable
859
+ data={data as CrudResponse<Record<string, unknown>>}
860
+ loading={loading || tableLoading}
861
+ onEdit={handleEdit}
862
+ onDelete={handleDelete}
863
+ onCustomAction={handleCustomAction}
864
+ onTableReady={setTableInstance}
865
+ onEmptyStateAction={{
866
+ onCreate: handleCreate,
867
+ onClearSearch: () => setSearch(""),
868
+ onClearFilters: clearFilters,
869
+ }}
870
+ getTranslation={(key: string) => t(key, key)}
871
+ />
872
+ ) : (
873
+ <div className="p-4">
874
+ <CrudCardView
875
+ config={config}
876
+ data={data as CrudResponse<Record<string, unknown>>}
877
+ permissions={permissions}
878
+ loading={loading || tableLoading}
879
+ onEdit={handleEdit}
880
+ onDelete={handleDelete}
881
+ onCustomAction={handleCustomAction}
882
+ onEmptyStateAction={{
883
+ onCreate: handleCreate,
884
+ onClearSearch: () => setSearch(""),
885
+ onClearFilters: clearFilters,
886
+ }}
887
+ />
888
+ </div>
889
+ )}
890
+ </div>
891
+ </div>
892
+ </div>
893
+
894
+ {permissions.create || permissions.update ? (
895
+ <>
896
+ {/* Dialog Mode */}
897
+ {((!editingRowId && createMode === "dialog") ||
898
+ (editingRowId && editMode === "dialog")) && (
899
+ <CrudDialog
900
+ open={dialogOpen}
901
+ onOpenChange={(open) => {
902
+ setDialogOpen(open);
903
+ if (!open) setEditingRowId(null);
904
+ }}
905
+ config={config}
906
+ initialData={editingData as Record<string, unknown> | undefined}
907
+ mode={editingRowId ? "edit" : "create"}
908
+ onSubmit={handleSubmit}
909
+ />
910
+ )}
911
+
912
+ {/* Sheet Mode */}
913
+ {((!editingRowId && createMode === "sheet") ||
914
+ (editingRowId && editMode === "sheet")) && (
915
+ <CrudSheet
916
+ open={dialogOpen}
917
+ onOpenChange={(open) => {
918
+ setDialogOpen(open);
919
+ if (!open) setEditingRowId(null);
920
+ }}
921
+ config={config}
922
+ initialData={editingData as Record<string, unknown> | undefined}
923
+ mode={editingRowId ? "edit" : "create"}
924
+ onSubmit={handleSubmit}
925
+ side={sheetSide}
926
+ />
927
+ )}
928
+ </>
929
+ ) : null}
930
+
931
+ {permissions.delete && (
932
+ <CrudDeleteDialog
933
+ open={deleteDialogOpen}
934
+ onOpenChange={setDeleteDialogOpen}
935
+ config={config}
936
+ itemData={deletingData as Record<string, unknown> | undefined}
937
+ itemCount={
938
+ bulkDeleteIds.length > 0 ? bulkDeleteIds.length : undefined
939
+ }
940
+ onConfirm={confirmDelete}
941
+ loading={deleteLoading}
942
+ translations={{
943
+ cancel: translations.cancel,
944
+ delete: translations.delete,
945
+ deleting: translations.deleting,
946
+ confirmDeleteTitle: translations.confirmDeleteTitle,
947
+ confirmBulkDeleteTitle: translations.confirmBulkDeleteTitle,
948
+ confirmDeleteDescription: translations.confirmDeleteDescription,
949
+ confirmBulkDeleteDescription:
950
+ translations.confirmBulkDeleteDescription,
951
+ }}
952
+ />
953
+ )}
954
+ </>
955
+ );
956
+ }
957
+
958
+ interface CrudPageProps {
959
+ config: EntityConfig;
960
+ permissions: CrudPermissions;
961
+ entityName?: string;
962
+ dictionary: DictionaryType;
963
+ customActions?: React.ReactNode;
964
+ }
965
+
966
+ export function CrudPage({
967
+ config,
968
+ permissions,
969
+ entityName,
970
+ dictionary,
971
+ customActions,
972
+ }: CrudPageProps) {
973
+ // Extract translations from dictionary for CrudProvider
974
+ const crudDict = (dictionary as Record<string, unknown>).crud as
975
+ | Record<string, unknown>
976
+ | undefined;
977
+ const commonDict = (crudDict?.common as Record<string, string>) || {};
978
+
979
+ const initialTranslations = {
980
+ edit: commonDict.edit,
981
+ delete: commonDict.delete,
982
+ save: commonDict.save,
983
+ cancel: commonDict.cancel,
984
+ create: commonDict.create,
985
+ update: commonDict.update,
986
+ creating: commonDict.creating,
987
+ updating: commonDict.updating,
988
+ deleting: commonDict.deleting,
989
+ savedSuccessfully: commonDict.savedSuccessfully,
990
+ createdSuccessfully: commonDict.createdSuccessfully,
991
+ updatedSuccessfully: commonDict.updatedSuccessfully,
992
+ confirmDeleteTitle: commonDict.confirmDeleteTitle,
993
+ confirmBulkDeleteTitle: commonDict.confirmBulkDeleteTitle,
994
+ confirmDeleteDescription: commonDict.confirmDeleteDescription,
995
+ confirmBulkDeleteDescription: commonDict.confirmBulkDeleteDescription,
996
+ deleteSelected: commonDict.deleteSelected,
997
+ actions: commonDict.actions,
998
+ add: commonDict.add,
999
+ addNew: commonDict.addNew,
1000
+ };
1001
+
1002
+ return (
1003
+ <CrudProvider
1004
+ initialConfig={config}
1005
+ initialPermissions={permissions}
1006
+ initialTranslations={initialTranslations}
1007
+ >
1008
+ <CrudPageContent
1009
+ config={config}
1010
+ permissions={permissions}
1011
+ entityName={entityName}
1012
+ dictionary={dictionary}
1013
+ customActions={customActions}
1014
+ />
1015
+ </CrudProvider>
1016
+ );
1017
+ }