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