@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,335 @@
1
+ /**
2
+ * CRUD Translation Utilities
3
+ * Translate EntityConfig ở server-side trước khi pass xuống client
4
+ */
5
+
6
+ import type { DictionaryType } from "../../hooks";
7
+ import type { EntityConfig, FieldConfig, FilterConfig } from "../../types";
8
+
9
+ /**
10
+ * Get nested value from object bằng dot notation
11
+ */
12
+ function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
13
+ return path.split(".").reduce((current, key) => {
14
+ return current && typeof current === "object" && key in current
15
+ ? (current as Record<string, unknown>)[key]
16
+ : undefined;
17
+ }, obj as unknown);
18
+ }
19
+
20
+ /**
21
+ * Translate a string với fallback
22
+ */
23
+ function translate(
24
+ key: string,
25
+ dictionary: Record<string, unknown>,
26
+ fallback: string,
27
+ ): string {
28
+ const value = getNestedValue(dictionary, key);
29
+ return typeof value === "string" ? value : fallback;
30
+ }
31
+
32
+ /**
33
+ * Translate EntityConfig với dictionary
34
+ * Chạy ở server-side trước khi pass xuống client
35
+ */
36
+ export function translateEntityConfig(
37
+ config: EntityConfig,
38
+ dictionary: DictionaryType,
39
+ locale: string,
40
+ entityName?: string,
41
+ ): EntityConfig {
42
+ const crudDict = (dictionary as Record<string, unknown>).crud as
43
+ | Record<string, unknown>
44
+ | undefined;
45
+ // Use entityName from URL (e.g., "materials") instead of config.name (e.g., "material")
46
+ // This ensures we match the dictionary key correctly
47
+ const dictKey = entityName || config.name;
48
+ const entitiesDict = (crudDict?.entities as Record<string, unknown>) || {};
49
+ const entityDict = entitiesDict[dictKey] as
50
+ | Record<string, unknown>
51
+ | undefined;
52
+ const commonDict = (crudDict?.common as Record<string, unknown>) || {};
53
+
54
+ // Translate entity-level labels
55
+ const translatedConfig: EntityConfig = {
56
+ ...config,
57
+ label:
58
+ (entityDict?.label as string) ||
59
+ translate(`crud.entities.${dictKey}.label`, dictionary, config.label),
60
+ pluralLabel:
61
+ (entityDict?.pluralLabel as string) ||
62
+ translate(
63
+ `crud.entities.${dictKey}.pluralLabel`,
64
+ dictionary,
65
+ config.pluralLabel,
66
+ ),
67
+ description:
68
+ (entityDict?.description as string) ||
69
+ translate(
70
+ `crud.entities.${dictKey}.description`,
71
+ dictionary,
72
+ config.description || "",
73
+ ),
74
+ };
75
+
76
+ // Translate fields
77
+ translatedConfig.fields = config.fields.map((field) => {
78
+ const fieldDict = (entityDict?.fields as Record<string, unknown>)?.[
79
+ field.name
80
+ ] as Record<string, unknown> | undefined;
81
+
82
+ const translatedField: FieldConfig = {
83
+ ...field,
84
+ label:
85
+ (fieldDict?.label as string) ||
86
+ translate(
87
+ `crud.entities.${dictKey}.fields.${field.name}.label`,
88
+ dictionary,
89
+ field.label,
90
+ ),
91
+ placeholder:
92
+ (fieldDict?.placeholder as string) ||
93
+ translate(
94
+ `crud.entities.${dictKey}.fields.${field.name}.placeholder`,
95
+ dictionary,
96
+ field.placeholder || "",
97
+ ),
98
+ helpText:
99
+ (fieldDict?.helpText as string) ||
100
+ translate(
101
+ `crud.entities.${dictKey}.fields.${field.name}.helpText`,
102
+ dictionary,
103
+ field.helpText || "",
104
+ ),
105
+ description:
106
+ (fieldDict?.description as string) ||
107
+ translate(
108
+ `crud.entities.${dictKey}.fields.${field.name}.description`,
109
+ dictionary,
110
+ field.description || "",
111
+ ),
112
+ };
113
+
114
+ // Translate options if they exist
115
+ if (field.options && field.options.length > 0) {
116
+ const optionsDict = (fieldDict?.options as Record<string, string>) || {};
117
+ // Fallback to common options
118
+ const commonOptionsDict =
119
+ (commonDict?.options as Record<string, string>) || {};
120
+
121
+ translatedField.options = field.options.map((opt) => {
122
+ const isObject = typeof opt === "object" && opt !== null;
123
+ const optValue = isObject ? opt.value : opt;
124
+ const optLabel = isObject ? opt.label : String(opt);
125
+ const valueStr = String(optValue);
126
+
127
+ if (!isObject) {
128
+ return (
129
+ optionsDict[valueStr] ||
130
+ commonOptionsDict[valueStr] ||
131
+ translate(
132
+ `crud.entities.${dictKey}.fields.${field.name}.options.${valueStr}`,
133
+ dictionary,
134
+ optLabel,
135
+ )
136
+ );
137
+ }
138
+
139
+ return {
140
+ ...opt,
141
+ label:
142
+ optionsDict[valueStr] ||
143
+ commonOptionsDict[valueStr] ||
144
+ translate(
145
+ `crud.entities.${dictKey}.fields.${field.name}.options.${valueStr}`,
146
+ dictionary,
147
+ optLabel.startsWith("crud.")
148
+ ? translate(optLabel, dictionary, optLabel)
149
+ : optLabel,
150
+ ),
151
+ };
152
+ });
153
+ }
154
+
155
+ return translatedField;
156
+ });
157
+
158
+ // Translate filters
159
+ if (config.filters && config.filters.length > 0) {
160
+ translatedConfig.filters = config.filters.map((filter) => {
161
+ const filterDict = (entityDict?.filters as Record<string, unknown>)?.[
162
+ filter.name
163
+ ] as Record<string, unknown> | undefined;
164
+
165
+ const translatedFilter: FilterConfig = {
166
+ ...filter,
167
+ label:
168
+ (filterDict?.label as string) ||
169
+ translate(
170
+ `crud.entities.${dictKey}.filters.${filter.name}.label`,
171
+ dictionary,
172
+ filter.label,
173
+ ),
174
+ };
175
+
176
+ // Translate filter options
177
+ if (filter.options && filter.options.length > 0) {
178
+ const optionsDict =
179
+ (filterDict?.options as Record<string, string>) || {};
180
+ translatedFilter.options = filter.options.map((opt) => {
181
+ const isObject = typeof opt === "object" && opt !== null;
182
+ const optValue = isObject ? opt.value : opt;
183
+ const optLabel = isObject ? opt.label : String(opt);
184
+ const valueStr = String(optValue);
185
+
186
+ if (!isObject) {
187
+ return {
188
+ label:
189
+ optionsDict[valueStr] ||
190
+ translate(
191
+ `crud.entities.${dictKey}.filters.${filter.name}.options.${valueStr}`,
192
+ dictionary,
193
+ optLabel,
194
+ ),
195
+ value: optValue,
196
+ };
197
+ }
198
+
199
+ return {
200
+ ...opt,
201
+ label:
202
+ optionsDict[valueStr] ||
203
+ translate(
204
+ `crud.entities.${dictKey}.filters.${filter.name}.options.${valueStr}`,
205
+ dictionary,
206
+ optLabel.startsWith("crud.")
207
+ ? translate(optLabel, dictionary, optLabel)
208
+ : optLabel,
209
+ ),
210
+ };
211
+ });
212
+ }
213
+
214
+ return translatedFilter;
215
+ });
216
+ }
217
+
218
+ // Translate rowActions
219
+ if (config.rowActions && config.rowActions.length > 0) {
220
+ const actionsDict =
221
+ (entityDict?.actions as Record<string, Record<string, string>>) || {};
222
+ translatedConfig.rowActions = config.rowActions.map((action) => {
223
+ const actionDict = actionsDict[action.action];
224
+ return {
225
+ ...action,
226
+ label:
227
+ actionDict?.label ||
228
+ translate(
229
+ `crud.entities.${dictKey}.actions.${action.action}.label`,
230
+ dictionary,
231
+ action.label,
232
+ ),
233
+ };
234
+ });
235
+ }
236
+
237
+ // Translate formSections
238
+ if (config.formSections && config.formSections.length > 0) {
239
+ const sectionsDict =
240
+ (entityDict?.sections as Record<string, Record<string, string>>) || {};
241
+ translatedConfig.formSections = config.formSections.map((section) => {
242
+ const sectionDict = sectionsDict[section.title] || {};
243
+ return {
244
+ ...section,
245
+ title:
246
+ sectionDict.title ||
247
+ translate(
248
+ `crud.entities.${dictKey}.sections.${section.title}.title`,
249
+ dictionary,
250
+ section.title,
251
+ ),
252
+ description:
253
+ sectionDict.description ||
254
+ translate(
255
+ `crud.entities.${dictKey}.sections.${section.title}.description`,
256
+ dictionary,
257
+ section.description || "",
258
+ ),
259
+ fields: section.fields, // Field names không cần translate
260
+ };
261
+ });
262
+ }
263
+
264
+ // Strip non-serializable properties to avoid Next.js serialization errors
265
+ // These should be handled by identifiers (strings) on the client
266
+ const { icon: _icon, ...serializableConfig } = translatedConfig;
267
+
268
+ // Cleanup fields
269
+ serializableConfig.fields = translatedConfig.fields.map((field) => {
270
+ const {
271
+ renderCell: _renderCell,
272
+ renderForm: _renderForm,
273
+ renderInput: _renderInput,
274
+ validation: _validation,
275
+ ...serializableField
276
+ } = field;
277
+ return serializableField;
278
+ });
279
+
280
+ // Cleanup row actions
281
+ if (translatedConfig.rowActions) {
282
+ serializableConfig.rowActions = translatedConfig.rowActions.map(
283
+ (action) => {
284
+ const {
285
+ handler: _handler,
286
+ visibleWhen: _visibleWhen,
287
+ ...serializableAction
288
+ } = action;
289
+ return serializableAction;
290
+ },
291
+ );
292
+ }
293
+
294
+ return serializableConfig as EntityConfig;
295
+ }
296
+
297
+ /**
298
+ * Get common CRUD translations
299
+ */
300
+ export function getCrudCommonTranslations(
301
+ dictionary: DictionaryType,
302
+ ): Record<string, string> {
303
+ const crudDict = (dictionary as Record<string, unknown>).crud as
304
+ | Record<string, unknown>
305
+ | undefined;
306
+ return (crudDict?.common as Record<string, string>) || {};
307
+ }
308
+
309
+ /**
310
+ * Translate CRUD message với parameters
311
+ */
312
+ export function translateCrudMessage(
313
+ dictionary: DictionaryType,
314
+ key: string,
315
+ params?: Record<string, string | number>,
316
+ ): string {
317
+ const crudDict = (dictionary as Record<string, unknown>).crud as
318
+ | Record<string, unknown>
319
+ | undefined;
320
+ const commonDict = (crudDict?.common as Record<string, unknown>) || {};
321
+ const messagesDict = (commonDict.messages as Record<string, string>) || {};
322
+ let message = messagesDict[key] || key;
323
+
324
+ // Replace parameters
325
+ if (params) {
326
+ Object.entries(params).forEach(([param, value]) => {
327
+ message = message.replace(
328
+ new RegExp(`\\{\\{${param}\\}\\}`, "g"),
329
+ String(value),
330
+ );
331
+ });
332
+ }
333
+
334
+ return message;
335
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * CRUD Types
3
+ * Shared types for CRUD operations
4
+ */
5
+
6
+ export interface CrudListResponse<T> {
7
+ total: number;
8
+ page: number;
9
+ pageSize: number;
10
+ items: T[];
11
+ }
@@ -0,0 +1,144 @@
1
+ import { cache } from "react";
2
+ import type { EntityConfig, FilterConfig, LocaleType } from "../../types";
3
+ import { CrudPage } from "../components/crud-page";
4
+ import { mergePermissions } from "../lib/permissions";
5
+ import { translateEntityConfig } from "../lib/translate-config";
6
+
7
+ interface EntityCrudPageProps {
8
+ entity: string;
9
+ lang: string;
10
+ config: EntityConfig;
11
+ session: any;
12
+ dictionary: any;
13
+ }
14
+
15
+ // Serialize config to make it safe for Client Components
16
+ // Removes non-serializable React components and functions
17
+ function serializeConfig(config: EntityConfig): Omit<EntityConfig, "icon"> {
18
+ const { icon, fields, filters, ...rest } = config;
19
+
20
+ // If icon exists but iconName doesn't, try to extract icon name from displayName
21
+ if (icon && !rest.iconName) {
22
+ const iconComponent = icon as { displayName?: string };
23
+ if (iconComponent.displayName) {
24
+ rest.iconName = iconComponent.displayName;
25
+ }
26
+ }
27
+
28
+ // Serialize fields
29
+ const serializedFields = fields.map((field) => {
30
+ const {
31
+ renderCell: _renderCell,
32
+ renderForm: _renderForm,
33
+ renderInput: _renderInput,
34
+ validation: _validation,
35
+ dataSource,
36
+ ...fieldRest
37
+ } = field;
38
+
39
+ if (dataSource?.transform) {
40
+ const { transform: _transform, ...dataSourceRest } = dataSource;
41
+ return {
42
+ ...fieldRest,
43
+ dataSource: dataSourceRest,
44
+ };
45
+ }
46
+
47
+ return dataSource ? { ...fieldRest, dataSource } : fieldRest;
48
+ });
49
+
50
+ // Serialize filters
51
+ const serializedFilters = filters?.map((filter) => {
52
+ const { renderFilter: _renderFilter, ...filterRest } = filter;
53
+ return filterRest as Omit<FilterConfig, "renderFilter">;
54
+ });
55
+
56
+ // Serialize rowActions
57
+ const serializedRowActions = rest.rowActions?.map((action) => {
58
+ const {
59
+ handler: _handler,
60
+ transformData: _transformData,
61
+ visibleWhen: _visibleWhen,
62
+ ...actionRest
63
+ } = action;
64
+ return actionRest;
65
+ });
66
+
67
+ return {
68
+ ...rest,
69
+ fields: serializedFields,
70
+ filters: serializedFilters,
71
+ rowActions: serializedRowActions,
72
+ };
73
+ }
74
+
75
+ // Process config (translate + serialize)
76
+ const getProcessedConfig = cache(
77
+ async (
78
+ config: EntityConfig,
79
+ entity: string,
80
+ lang: string,
81
+ dictionary: any,
82
+ ) => {
83
+ // Translate config
84
+ const translatedConfig = translateEntityConfig(
85
+ config,
86
+ dictionary,
87
+ lang,
88
+ entity,
89
+ );
90
+
91
+ // Serialize translated config
92
+ const serializedConfig = serializeConfig(translatedConfig);
93
+
94
+ return serializedConfig;
95
+ },
96
+ );
97
+
98
+ export async function EntityCrudPage({
99
+ entity,
100
+ lang,
101
+ config,
102
+ session,
103
+ dictionary,
104
+ }: EntityCrudPageProps) {
105
+ try {
106
+ const serializedConfig = await getProcessedConfig(
107
+ config,
108
+ entity,
109
+ lang,
110
+ dictionary,
111
+ );
112
+
113
+ // Get user permissions for this entity if available in session or passed explicitly?
114
+ // In vinhhoa/page.tsx: getCrudPermissions(session, entity) is called.
115
+ // We should probably rely on `session` having what we need OR generic permissions logic.
116
+ // The `mergePermissions` below merges generic config perms with USER perms.
117
+ // We need user perms.
118
+
119
+ // We can import getCrudPermissions from ../lib/permissions if it effectively uses session
120
+ const { getCrudPermissions } = await import("../lib/permissions");
121
+ const userPermissions = await getCrudPermissions(session, entity);
122
+
123
+ const permissions = mergePermissions(config.permissions, userPermissions);
124
+
125
+ return (
126
+ <CrudPage
127
+ config={serializedConfig as EntityConfig}
128
+ permissions={permissions}
129
+ entityName={entity}
130
+ dictionary={dictionary}
131
+ />
132
+ );
133
+ } catch (error) {
134
+ console.error(`Error loading CRUD page for ${entity}:`, error);
135
+ return (
136
+ <div className="p-4">
137
+ <h1 className="text-2xl font-bold">Error</h1>
138
+ <p className="text-muted-foreground">
139
+ {error instanceof Error ? error.message : "Failed to load page"}
140
+ </p>
141
+ </div>
142
+ );
143
+ }
144
+ }
@@ -0,0 +1,8 @@
1
+ // @goerp/core/crud/server
2
+ // Server-only CRUD utilities - no client components
3
+ // Use this in Server Components and API routes to avoid bundling client code
4
+
5
+ export {
6
+ getCrudPermissions,
7
+ mergePermissions,
8
+ } from './lib/permissions'
@@ -0,0 +1,142 @@
1
+ import React from "react";
2
+
3
+ // Icon mapping for common actions
4
+ export const iconMap: Record<string, React.ReactNode> = {
5
+ "shopping-cart": (
6
+ <svg
7
+ className="h-5 w-5"
8
+ fill="none"
9
+ stroke="currentColor"
10
+ viewBox="0 0 24 24"
11
+ >
12
+ <path
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ strokeWidth={2}
16
+ d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
17
+ />
18
+ </svg>
19
+ ),
20
+ users: (
21
+ <svg
22
+ className="h-5 w-5"
23
+ fill="none"
24
+ stroke="currentColor"
25
+ viewBox="0 0 24 24"
26
+ >
27
+ <path
28
+ strokeLinecap="round"
29
+ strokeLinejoin="round"
30
+ strokeWidth={2}
31
+ d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
32
+ />
33
+ </svg>
34
+ ),
35
+ package: (
36
+ <svg
37
+ className="h-5 w-5"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ viewBox="0 0 24 24"
41
+ >
42
+ <path
43
+ strokeLinecap="round"
44
+ strokeLinejoin="round"
45
+ strokeWidth={2}
46
+ d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"
47
+ />
48
+ </svg>
49
+ ),
50
+ "chart-bar": (
51
+ <svg
52
+ className="h-5 w-5"
53
+ fill="none"
54
+ stroke="currentColor"
55
+ viewBox="0 0 24 24"
56
+ >
57
+ <path
58
+ strokeLinecap="round"
59
+ strokeLinejoin="round"
60
+ strokeWidth={2}
61
+ d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
62
+ />
63
+ </svg>
64
+ ),
65
+ "document-text": (
66
+ <svg
67
+ className="h-5 w-5"
68
+ fill="none"
69
+ stroke="currentColor"
70
+ viewBox="0 0 24 24"
71
+ >
72
+ <path
73
+ strokeLinecap="round"
74
+ strokeLinejoin="round"
75
+ strokeWidth={2}
76
+ d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
77
+ />
78
+ </svg>
79
+ ),
80
+ cog: (
81
+ <svg
82
+ className="h-5 w-5"
83
+ fill="none"
84
+ stroke="currentColor"
85
+ viewBox="0 0 24 24"
86
+ >
87
+ <path
88
+ strokeLinecap="round"
89
+ strokeLinejoin="round"
90
+ strokeWidth={2}
91
+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
92
+ />
93
+ <path
94
+ strokeLinecap="round"
95
+ strokeLinejoin="round"
96
+ strokeWidth={2}
97
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
98
+ />
99
+ </svg>
100
+ ),
101
+ "currency-dollar": (
102
+ <svg
103
+ className="h-5 w-5"
104
+ fill="none"
105
+ stroke="currentColor"
106
+ viewBox="0 0 24 24"
107
+ >
108
+ <path
109
+ strokeLinecap="round"
110
+ strokeLinejoin="round"
111
+ strokeWidth={2}
112
+ d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
113
+ />
114
+ </svg>
115
+ ),
116
+ "clipboard-list": (
117
+ <svg
118
+ className="h-5 w-5"
119
+ fill="none"
120
+ stroke="currentColor"
121
+ viewBox="0 0 24 24"
122
+ >
123
+ <path
124
+ strokeLinecap="round"
125
+ strokeLinejoin="round"
126
+ strokeWidth={2}
127
+ d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"
128
+ />
129
+ </svg>
130
+ ),
131
+ };
132
+
133
+ export const colorClasses: Record<string, string> = {
134
+ blue: "bg-blue-500 hover:bg-blue-600 text-white",
135
+ green: "bg-green-500 hover:bg-green-600 text-white",
136
+ purple: "bg-purple-500 hover:bg-purple-600 text-white",
137
+ orange: "bg-orange-500 hover:bg-orange-600 text-white",
138
+ pink: "bg-pink-500 hover:bg-pink-600 text-white",
139
+ indigo: "bg-indigo-500 hover:bg-indigo-600 text-white",
140
+ teal: "bg-teal-500 hover:bg-teal-600 text-white",
141
+ red: "bg-red-500 hover:bg-red-600 text-white",
142
+ };