@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,49 @@
1
+ import { z } from "zod";
2
+
3
+ import { STATUS_ACTIVE, STATUS_INACTIVE, STATUS_VALUES } from "../configs";
4
+
5
+ /**
6
+ * Warehouse Validation Schema
7
+ */
8
+
9
+ export const warehouseSchema = z.object({
10
+ code: z
11
+ .string()
12
+ .min(1, "Mã kho là bắt buộc")
13
+ .max(50, "Mã kho tối đa 50 ký tự")
14
+ .trim(),
15
+
16
+ name: z
17
+ .string()
18
+ .min(1, "Tên kho là bắt buộc")
19
+ .max(200, "Tên kho tối đa 200 ký tự")
20
+ .trim(),
21
+
22
+ address: z
23
+ .string()
24
+ .min(1, "Địa chỉ là bắt buộc")
25
+ .max(500, "Địa chỉ tối đa 500 ký tự")
26
+ .trim(),
27
+
28
+ description: z
29
+ .string()
30
+ .max(1000, "Mô tả tối đa 1000 ký tự")
31
+ .optional()
32
+ .or(z.literal(""))
33
+ .transform((val) => val || ""),
34
+
35
+ status: z.preprocess((val) => {
36
+ // Hỗ trợ boolean từ switch field (true -> active, false -> inactive)
37
+ if (typeof val === "boolean") {
38
+ return val ? STATUS_ACTIVE : STATUS_INACTIVE;
39
+ }
40
+ // Hỗ trợ cả Active/Inactive cũ và chuyển về lowercase
41
+ if (val === "Active" || val === "Inactive") {
42
+ return val === "Active" ? STATUS_ACTIVE : STATUS_INACTIVE;
43
+ }
44
+ return val;
45
+ }, z.enum(STATUS_VALUES).default(STATUS_ACTIVE)),
46
+ });
47
+
48
+ export type WarehouseInput = z.infer<typeof warehouseSchema>;
49
+ export const warehouseUpdateSchema = warehouseSchema.partial();
@@ -0,0 +1,529 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { Plus, Pencil, Trash2, Search } from "lucide-react";
5
+ import { toast } from "sonner";
6
+ import { useForm } from "react-hook-form";
7
+ import type { SubmitHandler } from "react-hook-form";
8
+ import { zodResolver } from "@hookform/resolvers/zod";
9
+ import type * as z from "zod";
10
+
11
+ import type { SystemCategoryGroup, SystemCategory } from "../../types";
12
+ import { systemCategorySchema } from "../../schemas/system-category.schema";
13
+
14
+ import {
15
+ Button,
16
+ Table,
17
+ TableBody,
18
+ TableCell,
19
+ TableHead,
20
+ TableHeader,
21
+ TableRow,
22
+ Badge,
23
+ Dialog,
24
+ DialogContent,
25
+ DialogHeader,
26
+ DialogTitle,
27
+ Form,
28
+ FormControl,
29
+ FormField,
30
+ FormItem,
31
+ FormLabel,
32
+ FormMessage,
33
+ Input,
34
+ Textarea,
35
+ Switch,
36
+ Checkbox,
37
+ } from "../../../ui";
38
+
39
+ interface CategoryListProps {
40
+ group: SystemCategoryGroup;
41
+ lang: string;
42
+ }
43
+
44
+ export function CategoryList({ group, lang }: CategoryListProps) {
45
+ const [categories, setCategories] = useState<SystemCategory[]>([]);
46
+ const [isLoading, setIsLoading] = useState(true);
47
+ const [isCreateOpen, setIsCreateOpen] = useState(false);
48
+ const [editingCategory, setEditingCategory] = useState<SystemCategory | null>(
49
+ null,
50
+ );
51
+
52
+ // New State for Search and Selection
53
+ const [searchQuery, setSearchQuery] = useState("");
54
+ const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
55
+
56
+ const fetchCategories = useCallback(async () => {
57
+ try {
58
+ setIsLoading(true);
59
+ const res = await fetch(
60
+ `/api/system-categories?groupCode=${group.code}&pageSize=100`,
61
+ );
62
+ if (!res.ok) throw new Error("Failed to fetch categories");
63
+ const data = await res.json();
64
+ setCategories(data.items || []);
65
+ } catch (error) {
66
+ console.error(error);
67
+ toast.error("Không thể tải danh sách danh mục");
68
+ } finally {
69
+ setIsLoading(false);
70
+ setSelectedIds(new Set()); // Reset selection on refresh
71
+ }
72
+ }, [group.code]);
73
+
74
+ useEffect(() => {
75
+ fetchCategories();
76
+ }, [fetchCategories]);
77
+
78
+ // Filter categories
79
+ const filteredCategories = categories
80
+ .filter(
81
+ (c) =>
82
+ c.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
83
+ c.code.toLowerCase().includes(searchQuery.toLowerCase()),
84
+ )
85
+ .sort((a, b) => (a.order || 0) - (b.order || 0)); // Sort by order by default
86
+
87
+ const handleSelectAll = (checked: boolean) => {
88
+ if (checked) {
89
+ setSelectedIds(new Set(filteredCategories.map((c) => c.id)));
90
+ } else {
91
+ setSelectedIds(new Set());
92
+ }
93
+ };
94
+
95
+ const handleSelectRow = (id: string, checked: boolean) => {
96
+ const newSelected = new Set(selectedIds);
97
+ if (checked) {
98
+ newSelected.add(id);
99
+ } else {
100
+ newSelected.delete(id);
101
+ }
102
+ setSelectedIds(newSelected);
103
+ };
104
+
105
+ const handleDelete = async (category: SystemCategory) => {
106
+ if (!confirm("Bạn có chắc chắn muốn xóa danh mục này không?")) return;
107
+
108
+ try {
109
+ const res = await fetch(`/api/system-categories?id=${category.id}`, {
110
+ method: "DELETE",
111
+ });
112
+
113
+ if (!res.ok) throw new Error("Failed to delete category");
114
+
115
+ setCategories((prev) => prev.filter((c) => c.id !== category.id));
116
+ toast.success("Đã xóa danh mục thành công");
117
+ } catch (error) {
118
+ console.error(error);
119
+ toast.error("Xóa danh mục thất bại");
120
+ }
121
+ };
122
+
123
+ const handleBulkDelete = async () => {
124
+ if (selectedIds.size === 0) return;
125
+ if (
126
+ !confirm(
127
+ `Bạn có chắc chắn muốn xóa ${selectedIds.size} danh mục đã chọn?`,
128
+ )
129
+ )
130
+ return;
131
+
132
+ // Implement bulk delete logic (simulated with multiple calls for now, ideally API supports bulk id list)
133
+ // Assuming API supports single delete, we loop. Optimistically UI updates.
134
+ try {
135
+ await Promise.all(
136
+ Array.from(selectedIds).map((id) =>
137
+ fetch(`/api/system-categories?id=${id}`, { method: "DELETE" }),
138
+ ),
139
+ );
140
+
141
+ setCategories((prev) => prev.filter((c) => !selectedIds.has(c.id)));
142
+ setSelectedIds(new Set());
143
+ toast.success("Đã xóa các danh mục đã chọn");
144
+ } catch (error) {
145
+ toast.error("Có lỗi xảy ra khi xóa nhiều danh mục");
146
+ }
147
+ };
148
+
149
+ const handleCreateSuccess = (newCategory: SystemCategory) => {
150
+ setCategories((prev) => [newCategory, ...prev]);
151
+ setIsCreateOpen(false);
152
+ };
153
+
154
+ const handleUpdateSuccess = (updatedCategory: SystemCategory) => {
155
+ setCategories((prev) =>
156
+ prev.map((c) => (c.id === updatedCategory.id ? updatedCategory : c)),
157
+ );
158
+ setEditingCategory(null);
159
+ };
160
+
161
+ return (
162
+ <div className="flex h-full flex-col">
163
+ {/* Redesigned Header */}
164
+ <div className="mb-4 flex flex-col gap-4 border-b pb-4 sm:flex-row sm:items-center sm:justify-between">
165
+ <div>
166
+ <h2 className="text-xl font-bold">{group.name}</h2>
167
+ <p className="text-sm text-muted-foreground">{group.description}</p>
168
+ </div>
169
+ <div className="flex items-center gap-2">
170
+ <div className="relative w-full sm:w-64">
171
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
172
+ <Input
173
+ placeholder="Tìm kiếm danh mục..."
174
+ className="pl-8"
175
+ value={searchQuery}
176
+ onChange={(e) => setSearchQuery(e.target.value)}
177
+ />
178
+ </div>
179
+ <Button onClick={() => setIsCreateOpen(true)}>
180
+ <Plus className="mr-2 h-4 w-4" />
181
+ Thêm mới
182
+ </Button>
183
+ </div>
184
+ </div>
185
+
186
+ {/* Bulk Action Bar */}
187
+ {selectedIds.size > 0 && (
188
+ <div className="mb-2 flex items-center gap-2 rounded-md bg-muted p-2 text-sm">
189
+ <span className="font-medium text-muted-foreground">
190
+ Đã chọn {selectedIds.size} mục
191
+ </span>
192
+ <div className="ml-auto flex gap-2">
193
+ <Button
194
+ variant="ghost"
195
+ size="sm"
196
+ className="h-8 text-destructive hover:text-destructive hover:bg-destructive/10"
197
+ onClick={handleBulkDelete}
198
+ >
199
+ <Trash2 className="mr-2 h-4 w-4" />
200
+ Xóa đã chọn
201
+ </Button>
202
+ <Button
203
+ variant="ghost"
204
+ size="sm"
205
+ className="h-8"
206
+ onClick={() => setSelectedIds(new Set())}
207
+ >
208
+ Hủy chọn
209
+ </Button>
210
+ </div>
211
+ </div>
212
+ )}
213
+
214
+ <div className="flex-1 overflow-auto rounded-md border">
215
+ <Table>
216
+ <TableHeader>
217
+ <TableRow>
218
+ <TableHead className="w-[40px]">
219
+ <Checkbox
220
+ checked={
221
+ filteredCategories.length > 0 &&
222
+ selectedIds.size === filteredCategories.length
223
+ }
224
+ onCheckedChange={(checked) => handleSelectAll(!!checked)}
225
+ />
226
+ </TableHead>
227
+ <TableHead className="w-[80px]">Mã</TableHead>
228
+ <TableHead>Tên danh mục</TableHead>
229
+ <TableHead className="w-[100px] text-center">Thứ tự</TableHead>
230
+ <TableHead className="w-[120px]">Trạng thái</TableHead>
231
+ <TableHead className="w-[100px] text-right">Hành động</TableHead>
232
+ </TableRow>
233
+ </TableHeader>
234
+ <TableBody>
235
+ {isLoading ? (
236
+ <TableRow>
237
+ <TableCell colSpan={6} className="text-center py-8">
238
+ Đang tải...
239
+ </TableCell>
240
+ </TableRow>
241
+ ) : filteredCategories.length === 0 ? (
242
+ <TableRow>
243
+ <TableCell
244
+ colSpan={6}
245
+ className="text-center py-8 text-muted-foreground"
246
+ >
247
+ {searchQuery
248
+ ? "Không tìm thấy kết quả"
249
+ : "Chưa có danh mục nào trong nhóm này"}
250
+ </TableCell>
251
+ </TableRow>
252
+ ) : (
253
+ filteredCategories.map((category) => (
254
+ <TableRow
255
+ key={category.id}
256
+ className={selectedIds.has(category.id) ? "bg-muted/50" : ""}
257
+ >
258
+ <TableCell>
259
+ <Checkbox
260
+ checked={selectedIds.has(category.id)}
261
+ onCheckedChange={(checked) =>
262
+ handleSelectRow(category.id, !!checked)
263
+ }
264
+ />
265
+ </TableCell>
266
+ <TableCell className="font-medium">{category.code}</TableCell>
267
+ <TableCell>
268
+ <div className="font-medium">{category.name}</div>
269
+ {category.description && (
270
+ <div className="text-xs text-muted-foreground truncate max-w-[300px]">
271
+ {category.description}
272
+ </div>
273
+ )}
274
+ </TableCell>
275
+ <TableCell className="text-center">
276
+ <Badge variant="outline" className="font-mono">
277
+ {category.order ?? 0}
278
+ </Badge>
279
+ </TableCell>
280
+ <TableCell>
281
+ <Badge
282
+ variant={
283
+ category.status === "active" ? "default" : "secondary"
284
+ }
285
+ >
286
+ {category.status === "active" ? "Hoạt động" : "Ngừng HĐ"}
287
+ </Badge>
288
+ </TableCell>
289
+ <TableCell className="text-right">
290
+ <div className="flex justify-end gap-2">
291
+ <Button
292
+ variant="ghost"
293
+ size="icon"
294
+ className="h-8 w-8"
295
+ onClick={() => setEditingCategory(category)}
296
+ >
297
+ <Pencil className="h-4 w-4" />
298
+ </Button>
299
+ <Button
300
+ variant="ghost"
301
+ size="icon"
302
+ className="h-8 w-8 text-destructive hover:text-destructive"
303
+ onClick={() => handleDelete(category)}
304
+ >
305
+ <Trash2 className="h-4 w-4" />
306
+ </Button>
307
+ </div>
308
+ </TableCell>
309
+ </TableRow>
310
+ ))
311
+ )}
312
+ </TableBody>
313
+ </Table>
314
+ </div>
315
+
316
+ <CategoryDialog
317
+ mode="create"
318
+ open={isCreateOpen}
319
+ onOpenChange={setIsCreateOpen}
320
+ groupCode={group.code}
321
+ onSuccess={handleCreateSuccess}
322
+ />
323
+
324
+ {editingCategory && (
325
+ <CategoryDialog
326
+ mode="edit"
327
+ open={!!editingCategory}
328
+ onOpenChange={(open) => !open && setEditingCategory(null)}
329
+ groupCode={group.code}
330
+ category={editingCategory}
331
+ onSuccess={handleUpdateSuccess}
332
+ />
333
+ )}
334
+ </div>
335
+ );
336
+ }
337
+
338
+ // Dialog Component
339
+ interface CategoryDialogProps {
340
+ mode: "create" | "edit";
341
+ open: boolean;
342
+ onOpenChange: (open: boolean) => void;
343
+ groupCode: string;
344
+ category?: SystemCategory;
345
+ onSuccess: (category: SystemCategory) => void;
346
+ }
347
+
348
+ function CategoryDialog({
349
+ mode,
350
+ open,
351
+ onOpenChange,
352
+ groupCode,
353
+ category,
354
+ onSuccess,
355
+ }: CategoryDialogProps) {
356
+ const form = useForm<z.infer<typeof systemCategorySchema>>({
357
+ resolver: zodResolver(systemCategorySchema),
358
+ defaultValues: {
359
+ code: category?.code || "",
360
+ name: category?.name || "",
361
+ groupCode: groupCode,
362
+ description: category?.description || "",
363
+ status: category?.status === "inactive" ? "inactive" : "active",
364
+ order: category?.order || 0,
365
+ },
366
+ });
367
+
368
+ // Reset form when opening create mode to ensure groupCode is set correctly if switching groups
369
+ useEffect(() => {
370
+ if (open && mode === "create") {
371
+ form.reset({
372
+ code: "",
373
+ name: "",
374
+ groupCode: groupCode,
375
+ description: "",
376
+ status: "active",
377
+ order: 0,
378
+ });
379
+ }
380
+ }, [open, mode, groupCode, form]);
381
+
382
+ const onSubmit: SubmitHandler<z.infer<typeof systemCategorySchema>> = async (
383
+ values,
384
+ ) => {
385
+ try {
386
+ const url =
387
+ mode === "create"
388
+ ? "/api/system-categories"
389
+ : `/api/system-categories?id=${category?.id}`;
390
+ const method = mode === "create" ? "POST" : "PUT";
391
+
392
+ const payload =
393
+ mode === "edit" ? { ...values, id: category?.id } : values;
394
+
395
+ const res = await fetch(url, {
396
+ method,
397
+ headers: { "Content-Type": "application/json" },
398
+ body: JSON.stringify(payload),
399
+ });
400
+
401
+ if (!res.ok) {
402
+ const errorData = await res.json();
403
+ throw new Error(errorData.message || "Action failed");
404
+ }
405
+
406
+ const data = await res.json();
407
+ toast.success(
408
+ mode === "create"
409
+ ? "Tạo danh mục thành công"
410
+ : "Cập nhật danh mục thành công",
411
+ );
412
+ onSuccess(data);
413
+ } catch (error) {
414
+ toast.error(error instanceof Error ? error.message : "Có lỗi xảy ra");
415
+ }
416
+ };
417
+
418
+ return (
419
+ <Dialog open={open} onOpenChange={onOpenChange}>
420
+ <DialogContent>
421
+ <DialogHeader>
422
+ <DialogTitle>
423
+ {mode === "create" ? "Thêm danh mục" : "Chỉnh sửa danh mục"}
424
+ </DialogTitle>
425
+ </DialogHeader>
426
+
427
+ <Form {...form}>
428
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
429
+ <div className="grid grid-cols-2 gap-4">
430
+ <FormField
431
+ control={form.control}
432
+ name="code"
433
+ render={({ field }) => (
434
+ <FormItem>
435
+ <FormLabel>Mã danh mục</FormLabel>
436
+ <FormControl>
437
+ <Input
438
+ placeholder="Nhập mã"
439
+ {...field}
440
+ disabled={mode === "edit"}
441
+ />
442
+ </FormControl>
443
+ <FormMessage />
444
+ </FormItem>
445
+ )}
446
+ />
447
+ <FormField
448
+ control={form.control}
449
+ name="order"
450
+ render={({ field }) => (
451
+ <FormItem>
452
+ <FormLabel>Thứ tự</FormLabel>
453
+ <FormControl>
454
+ <Input
455
+ type="number"
456
+ {...field}
457
+ onChange={(e) => field.onChange(e.target.valueAsNumber)}
458
+ />
459
+ </FormControl>
460
+ <FormMessage />
461
+ </FormItem>
462
+ )}
463
+ />
464
+ </div>
465
+
466
+ <FormField
467
+ control={form.control}
468
+ name="name"
469
+ render={({ field }) => (
470
+ <FormItem>
471
+ <FormLabel>Tên danh mục</FormLabel>
472
+ <FormControl>
473
+ <Input placeholder="Nhập tên danh mục" {...field} />
474
+ </FormControl>
475
+ <FormMessage />
476
+ </FormItem>
477
+ )}
478
+ />
479
+
480
+ <FormField
481
+ control={form.control}
482
+ name="description"
483
+ render={({ field }) => (
484
+ <FormItem>
485
+ <FormLabel>Mô tả</FormLabel>
486
+ <FormControl>
487
+ <Textarea placeholder="Mô tả danh mục" {...field} />
488
+ </FormControl>
489
+ <FormMessage />
490
+ </FormItem>
491
+ )}
492
+ />
493
+
494
+ <FormField
495
+ control={form.control}
496
+ name="status"
497
+ render={({ field }) => (
498
+ <FormItem className="flex items-center justify-between rounded-lg border p-3 shadow-sm">
499
+ <div className="space-y-0.5">
500
+ <FormLabel>Trạng thái</FormLabel>
501
+ </div>
502
+ <FormControl>
503
+ <Switch
504
+ checked={field.value === "active"}
505
+ onCheckedChange={(checked) =>
506
+ field.onChange(checked ? "active" : "inactive")
507
+ }
508
+ />
509
+ </FormControl>
510
+ </FormItem>
511
+ )}
512
+ />
513
+
514
+ <div className="flex justify-end gap-2">
515
+ <Button
516
+ type="button"
517
+ variant="outline"
518
+ onClick={() => onOpenChange(false)}
519
+ >
520
+ Hủy
521
+ </Button>
522
+ <Button type="submit">Lưu</Button>
523
+ </div>
524
+ </form>
525
+ </Form>
526
+ </DialogContent>
527
+ </Dialog>
528
+ );
529
+ }
@@ -0,0 +1,89 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Search } from "lucide-react";
5
+
6
+ import type { SystemCategoryGroup } from "../../types";
7
+ import { Input } from "../../../ui";
8
+ import { GroupSidebar } from "./group-sidebar";
9
+ import { CategoryList } from "./category-list";
10
+
11
+ interface CategoryManagerProps {
12
+ initialGroups: SystemCategoryGroup[];
13
+ lang: string;
14
+ }
15
+
16
+ export function CategoryManager({ initialGroups, lang }: CategoryManagerProps) {
17
+ const [groups, setGroups] = useState(initialGroups);
18
+ const [selectedGroup, setSelectedGroup] =
19
+ useState<SystemCategoryGroup | null>(groups.length > 0 ? groups[0] : null);
20
+ const [searchTerm, setSearchTerm] = useState("");
21
+
22
+ const filteredGroups = groups.filter(
23
+ (g) =>
24
+ g.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
25
+ g.code.toLowerCase().includes(searchTerm.toLowerCase()),
26
+ );
27
+
28
+ const handleCreate = (group: SystemCategoryGroup) => {
29
+ setGroups((prev) => [group, ...prev]);
30
+ setSelectedGroup(group);
31
+ };
32
+
33
+ const handleUpdate = (group: SystemCategoryGroup) => {
34
+ setGroups((prev) => prev.map((g) => (g.id === group.id ? group : g)));
35
+ if (selectedGroup?.id === group.id) {
36
+ setSelectedGroup(group);
37
+ }
38
+ };
39
+
40
+ const handleDelete = (groupId: string) => {
41
+ setGroups((prev) => prev.filter((g) => g.id !== groupId));
42
+ if (selectedGroup?.id === groupId) {
43
+ setSelectedGroup(null);
44
+ }
45
+ };
46
+
47
+ return (
48
+ <div className="flex h-[calc(100vh-4rem)] flex-col gap-4 p-4 md:flex-row">
49
+ <div className="flex w-full flex-col gap-4 md:w-80 lg:w-96">
50
+ <div className="relative">
51
+ <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
52
+ <Input
53
+ placeholder="Tìm kiếm nhóm..."
54
+ className="pl-8"
55
+ value={searchTerm}
56
+ onChange={(e) => setSearchTerm(e.target.value)}
57
+ />
58
+ </div>
59
+
60
+ <div className="flex-1 rounded-lg border bg-card p-2 shadow-sm">
61
+ <GroupSidebar
62
+ groups={filteredGroups}
63
+ selectedGroup={selectedGroup}
64
+ onSelectGroup={setSelectedGroup}
65
+ onCreate={handleCreate}
66
+ onUpdate={handleUpdate}
67
+ onDelete={handleDelete}
68
+ />
69
+ </div>
70
+ </div>
71
+
72
+ <div className="flex-1 rounded-lg border bg-card p-6 shadow-sm">
73
+ {selectedGroup ? (
74
+ <CategoryList
75
+ key={selectedGroup.id}
76
+ group={selectedGroup}
77
+ lang={lang}
78
+ />
79
+ ) : (
80
+ <div className="flex h-full items-center justify-center text-muted-foreground">
81
+ {groups.length === 0
82
+ ? "Chưa có nhóm danh mục nào. Hãy tạo nhóm mới."
83
+ : "Chọn một nhóm để xem danh mục"}
84
+ </div>
85
+ )}
86
+ </div>
87
+ </div>
88
+ );
89
+ }