@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,372 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Loader2, RefreshCw, Plus } from "lucide-react";
6
+ import { toast } from "sonner";
7
+ import { cn } from "../../../../utils";
8
+ import { Button } from "../../../../ui/primitives/button";
9
+
10
+ import { SETTINGS_GROUPS } from "../../../components/settings/settings-groups";
11
+ import { SettingsSidebar } from "../../../components/settings/settings-sidebar";
12
+ import { SettingsSearch } from "../../../components/settings/settings-search";
13
+ import { SettingsSection } from "../../../components/settings/settings-section";
14
+ import { SettingField } from "../../../components/settings/setting-field";
15
+ import type { SettingConfig } from "../../../types";
16
+ import { SettingFormDialog } from "../../../components/settings/setting-form-dialog";
17
+ import { DeleteConfirmDialog, SuspendConfirmDialog } from "../../../components/settings/setting-dialogs";
18
+
19
+ interface SettingsPageProps {
20
+ lang: string;
21
+ }
22
+
23
+ interface SettingsData {
24
+ items: SettingConfig[];
25
+ groupedItems: Record<string, SettingConfig[]>;
26
+ }
27
+
28
+ export function SettingsPage({ lang }: SettingsPageProps) {
29
+ const router = useRouter();
30
+ const [loading, setLoading] = React.useState(true);
31
+ const [settings, setSettings] = React.useState<SettingsData | null>(null);
32
+ const [activeGroup, setActiveGroup] = React.useState(
33
+ SETTINGS_GROUPS[0]?.id || "general",
34
+ );
35
+ const [searchQuery, setSearchQuery] = React.useState("");
36
+ const [highlightedKey, setHighlightedKey] = React.useState<string | null>(
37
+ null,
38
+ );
39
+
40
+ // Dialog states
41
+ const [formDialogOpen, setFormDialogOpen] = React.useState(false);
42
+ const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
43
+ const [suspendDialogOpen, setSuspendDialogOpen] = React.useState(false);
44
+ const [selectedSetting, setSelectedSetting] =
45
+ React.useState<SettingConfig | null>(null);
46
+
47
+ // Fetch settings
48
+ const fetchSettings = React.useCallback(async () => {
49
+ setLoading(true);
50
+ try {
51
+ const res = await fetch("/api/admin/system/settings/all");
52
+ if (!res.ok) throw new Error("Failed to fetch settings");
53
+ const data = await res.json();
54
+
55
+ // Group items by category
56
+ const items: SettingConfig[] = data.data || [];
57
+ const groupedItems: Record<string, SettingConfig[]> = {};
58
+
59
+ for (const item of items) {
60
+ const category = item.category || "general";
61
+ if (!groupedItems[category]) {
62
+ groupedItems[category] = [];
63
+ }
64
+ groupedItems[category].push(item);
65
+ }
66
+
67
+ setSettings({ items, groupedItems });
68
+ } catch (error) {
69
+ console.error("Error fetching settings:", error);
70
+ toast.error("Không thể tải cấu hình hệ thống");
71
+ } finally {
72
+ setLoading(false);
73
+ }
74
+ }, []);
75
+
76
+ React.useEffect(() => {
77
+ fetchSettings();
78
+ }, [fetchSettings]);
79
+
80
+ // Handle setting update
81
+ const handleUpdate = React.useCallback(async (key: string, value: string) => {
82
+ try {
83
+ const res = await fetch("/api/admin/system/settings/update", {
84
+ method: "POST",
85
+ headers: { "Content-Type": "application/json" },
86
+ body: JSON.stringify({ key, value }),
87
+ });
88
+
89
+ if (!res.ok) throw new Error("Failed to update setting");
90
+
91
+ // Update local state
92
+ setSettings((prev) => {
93
+ if (!prev) return prev;
94
+ const items = prev.items.map((item) =>
95
+ item.key === key ? { ...item, value } : item,
96
+ );
97
+ const groupedItems: Record<string, SettingConfig[]> = {};
98
+ for (const item of items) {
99
+ const category = item.category || "general";
100
+ if (!groupedItems[category]) {
101
+ groupedItems[category] = [];
102
+ }
103
+ groupedItems[category].push(item);
104
+ }
105
+ return { items, groupedItems };
106
+ });
107
+ } catch (error) {
108
+ console.error("Error updating setting:", error);
109
+ throw error;
110
+ }
111
+ }, []);
112
+
113
+ // Action handlers
114
+ const handleEdit = React.useCallback((setting: SettingConfig) => {
115
+ setSelectedSetting(setting);
116
+ setFormDialogOpen(true);
117
+ }, []);
118
+
119
+ const handleDelete = React.useCallback((setting: SettingConfig) => {
120
+ setSelectedSetting(setting);
121
+ setDeleteDialogOpen(true);
122
+ }, []);
123
+
124
+ const handleToggleStatus = React.useCallback((setting: SettingConfig) => {
125
+ setSelectedSetting(setting);
126
+ setSuspendDialogOpen(true);
127
+ }, []);
128
+
129
+ const handleAddNew = React.useCallback(() => {
130
+ setSelectedSetting(null);
131
+ setFormDialogOpen(true);
132
+ }, []);
133
+
134
+ const handleDialogSuccess = React.useCallback(() => {
135
+ fetchSettings();
136
+ }, [fetchSettings]);
137
+
138
+ // Filter settings based on search query and active group
139
+ const filteredSettings = React.useMemo(() => {
140
+ if (!settings) return [];
141
+
142
+ let items = settings.items;
143
+
144
+ // Filter by active group
145
+ const group = SETTINGS_GROUPS.find((g) => g.id === activeGroup);
146
+ if (group) {
147
+ items = items.filter((item) => {
148
+ // Special case: phu_quy_* settings should be included in "integrations" group
149
+ const effectiveCategory = item.key.startsWith("phu_quy_") ? "phu_quy" : item.category;
150
+ return group.categories.includes(item.category) || group.categories.includes(effectiveCategory);
151
+ });
152
+ }
153
+
154
+ // Filter by search query
155
+ if (searchQuery.trim()) {
156
+ const query = searchQuery.toLowerCase();
157
+ items = items.filter(
158
+ (item) =>
159
+ item.key.toLowerCase().includes(query) ||
160
+ item.description?.toLowerCase().includes(query) ||
161
+ item.category.toLowerCase().includes(query),
162
+ );
163
+ }
164
+
165
+ return items;
166
+ }, [settings, activeGroup, searchQuery]);
167
+
168
+ // Group filtered settings by category
169
+ // Special handling: Group settings with key starting with "phu_quy_" into "phu_quy" category
170
+ const groupedFilteredSettings = React.useMemo(() => {
171
+ const grouped: Record<string, SettingConfig[]> = {};
172
+ for (const item of filteredSettings) {
173
+ // Special case: Group phu_quy_* settings together
174
+ let category = item.category || "general";
175
+ if (item.key.startsWith("phu_quy_")) {
176
+ category = "phu_quy";
177
+ }
178
+
179
+ if (!grouped[category]) {
180
+ grouped[category] = [];
181
+ }
182
+ grouped[category].push(item);
183
+ }
184
+ return grouped;
185
+ }, [filteredSettings]);
186
+
187
+ // Handle search keyboard navigation
188
+ const handleSearchKeyDown = React.useCallback(
189
+ (e: React.KeyboardEvent) => {
190
+ if (e.key === "Enter" && filteredSettings.length > 0) {
191
+ const firstItem = filteredSettings[0];
192
+ setHighlightedKey(firstItem.key);
193
+ document.getElementById(`setting-${firstItem.key}`)?.scrollIntoView({
194
+ behavior: "smooth",
195
+ block: "center",
196
+ });
197
+ setTimeout(() => setHighlightedKey(null), 3000);
198
+ }
199
+ },
200
+ [filteredSettings],
201
+ );
202
+
203
+ if (loading) {
204
+ return (
205
+ <div className="flex items-center justify-center min-h-[400px]">
206
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
207
+ </div>
208
+ );
209
+ }
210
+
211
+ return (
212
+ <div className="min-h-screen">
213
+ {/* Header */}
214
+ <div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 sticky top-0 z-10">
215
+ <div className="container flex items-center justify-between h-16 px-4">
216
+ <div>
217
+ <h1 className="text-xl font-semibold">Cài đặt hệ thống</h1>
218
+ <p className="text-sm text-muted-foreground">
219
+ Quản lý cấu hình và tùy chỉnh hệ thống
220
+ </p>
221
+ </div>
222
+ <div className="flex items-center gap-2">
223
+ <Button
224
+ variant="outline"
225
+ size="sm"
226
+ onClick={fetchSettings}
227
+ disabled={loading}
228
+ >
229
+ <RefreshCw
230
+ className={cn("h-4 w-4 mr-2", loading && "animate-spin")}
231
+ />
232
+ Làm mới
233
+ </Button>
234
+ <Button size="sm" onClick={handleAddNew}>
235
+ <Plus className="h-4 w-4 mr-2" />
236
+ Thêm cấu hình
237
+ </Button>
238
+ </div>
239
+ </div>
240
+ </div>
241
+
242
+ {/* Main Content */}
243
+ <div className="container px-4 py-6">
244
+ <div className="flex gap-8">
245
+ {/* Sidebar */}
246
+ <SettingsSidebar
247
+ groups={SETTINGS_GROUPS}
248
+ activeGroup={activeGroup}
249
+ onGroupChange={setActiveGroup}
250
+ className="hidden md:block"
251
+ />
252
+
253
+ {/* Content */}
254
+ <div className="flex-1 min-w-0">
255
+ {/* Search */}
256
+ <SettingsSearch
257
+ value={searchQuery}
258
+ onChange={setSearchQuery}
259
+ onKeyDown={handleSearchKeyDown}
260
+ resultCount={filteredSettings.length}
261
+ className="mb-6"
262
+ />
263
+
264
+ {/* Settings Groups */}
265
+ <div className="space-y-8">
266
+ {Object.entries(groupedFilteredSettings).length === 0 ? (
267
+ <div className="text-center py-12">
268
+ <p className="text-muted-foreground">
269
+ {searchQuery
270
+ ? "Không tìm thấy cấu hình phù hợp"
271
+ : "Chưa có cấu hình nào trong nhóm này"}
272
+ </p>
273
+ <Button
274
+ variant="outline"
275
+ size="sm"
276
+ className="mt-4"
277
+ onClick={handleAddNew}
278
+ >
279
+ <Plus className="h-4 w-4 mr-2" />
280
+ Thêm cấu hình mới
281
+ </Button>
282
+ </div>
283
+ ) : (
284
+ Object.entries(groupedFilteredSettings).map(
285
+ ([category, items]) => (
286
+ <SettingsSection
287
+ key={category}
288
+ id={category}
289
+ title={formatCategoryName(category)}
290
+ count={items.length}
291
+ >
292
+ {items.map((item) => (
293
+ <SettingField
294
+ key={item.id}
295
+ setting={item}
296
+ onUpdate={handleUpdate}
297
+ onEdit={handleEdit}
298
+ onDelete={handleDelete}
299
+ onToggleStatus={handleToggleStatus}
300
+ isHighlighted={highlightedKey === item.key}
301
+ />
302
+ ))}
303
+ </SettingsSection>
304
+ ),
305
+ )
306
+ )}
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </div>
311
+
312
+ {/* Dialogs */}
313
+ <SettingFormDialog
314
+ open={formDialogOpen}
315
+ onOpenChange={setFormDialogOpen}
316
+ setting={selectedSetting}
317
+ onSuccess={handleDialogSuccess}
318
+ />
319
+ <DeleteConfirmDialog
320
+ open={deleteDialogOpen}
321
+ onOpenChange={setDeleteDialogOpen}
322
+ setting={selectedSetting}
323
+ onSuccess={handleDialogSuccess}
324
+ />
325
+ <SuspendConfirmDialog
326
+ open={suspendDialogOpen}
327
+ onOpenChange={setSuspendDialogOpen}
328
+ setting={selectedSetting}
329
+ onSuccess={handleDialogSuccess}
330
+ />
331
+ </div>
332
+ );
333
+ }
334
+
335
+ // Helper function to format category names
336
+ function formatCategoryName(category: string): string {
337
+ const names: Record<string, string> = {
338
+ general: "Cấu hình chung",
339
+ app: "Ứng dụng",
340
+ system: "Hệ thống",
341
+ pricing: "Giá cả",
342
+ sales: "Bán hàng",
343
+ purchase: "Mua hàng",
344
+ inventory: "Kho hàng",
345
+ zalo: "Zalo",
346
+ telegram: "Telegram",
347
+ email: "Email",
348
+ sms: "SMS",
349
+ webhook: "Webhook",
350
+ phu_quy: "Phú Quý",
351
+ integrations: "Tích hợp",
352
+ notifications: "Thông báo",
353
+ alerts: "Cảnh báo",
354
+ security: "Bảo mật",
355
+ auth: "Xác thực",
356
+ audit: "Nhật ký",
357
+ automation: "Tự động hóa",
358
+ cron: "Lịch trình",
359
+ jobs: "Công việc",
360
+ scheduler: "Lập lịch",
361
+ theme: "Giao diện",
362
+ ui: "Hiển thị",
363
+ display: "Màn hình",
364
+ debug: "Debug",
365
+ cache: "Cache",
366
+ performance: "Hiệu năng",
367
+ logs: "Logs",
368
+ };
369
+ return (
370
+ names[category] || category.charAt(0).toUpperCase() + category.slice(1)
371
+ );
372
+ }
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Search, Command } from "lucide-react";
5
+ import { cn } from "../../../../utils";
6
+ import { Input } from "../../../../ui/primitives/input";
7
+
8
+ interface SettingsSearchProps {
9
+ value: string;
10
+ onChange: (value: string) => void;
11
+ onKeyDown?: (e: React.KeyboardEvent) => void;
12
+ className?: string;
13
+ resultCount?: number;
14
+ }
15
+
16
+ export function SettingsSearch({
17
+ value,
18
+ onChange,
19
+ onKeyDown,
20
+ className,
21
+ resultCount,
22
+ }: SettingsSearchProps) {
23
+ const inputRef = React.useRef<HTMLInputElement>(null);
24
+
25
+ // Global keyboard shortcut (Cmd+K / Ctrl+K)
26
+ React.useEffect(() => {
27
+ const handleKeyDown = (e: KeyboardEvent) => {
28
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
29
+ e.preventDefault();
30
+ inputRef.current?.focus();
31
+ }
32
+ if (e.key === "Escape" && document.activeElement === inputRef.current) {
33
+ inputRef.current?.blur();
34
+ onChange("");
35
+ }
36
+ };
37
+
38
+ document.addEventListener("keydown", handleKeyDown);
39
+ return () => document.removeEventListener("keydown", handleKeyDown);
40
+ }, [onChange]);
41
+
42
+ return (
43
+ <div className={cn("relative", className)}>
44
+ <div className="relative">
45
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
46
+ <Input
47
+ ref={inputRef}
48
+ type="text"
49
+ placeholder="Tìm kiếm cấu hình..."
50
+ value={value}
51
+ onChange={(e) => onChange(e.target.value)}
52
+ onKeyDown={onKeyDown}
53
+ className={cn(
54
+ "pl-9 pr-20 h-11 bg-muted/50 border-0 focus-visible:bg-background focus-visible:ring-1",
55
+ "placeholder:text-muted-foreground/60 text-sm",
56
+ )}
57
+ />
58
+ <div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1">
59
+ {resultCount !== undefined && value && (
60
+ <span className="text-xs text-muted-foreground mr-2">
61
+ {resultCount} kết quả
62
+ </span>
63
+ )}
64
+ <kbd className="hidden sm:inline-flex items-center gap-0.5 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground bg-muted rounded border">
65
+ <Command className="h-2.5 w-2.5" />K
66
+ </kbd>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronDown } from "lucide-react";
5
+ import { cn } from "../../../../utils";
6
+ import {
7
+ Collapsible,
8
+ CollapsibleContent,
9
+ CollapsibleTrigger,
10
+ } from "../../../../ui/data-display/collapsible";
11
+
12
+ interface SettingsSectionProps {
13
+ id: string;
14
+ title: string;
15
+ description?: string;
16
+ count?: number;
17
+ defaultOpen?: boolean;
18
+ children: React.ReactNode;
19
+ className?: string;
20
+ }
21
+
22
+ export function SettingsSection({
23
+ id,
24
+ title,
25
+ description,
26
+ count,
27
+ defaultOpen = true,
28
+ children,
29
+ className,
30
+ }: SettingsSectionProps) {
31
+ const [isOpen, setIsOpen] = React.useState(defaultOpen);
32
+
33
+ return (
34
+ <Collapsible
35
+ open={isOpen}
36
+ onOpenChange={setIsOpen}
37
+ className={cn("space-y-3", className)}
38
+ >
39
+ <div id={`section-${id}`} className="scroll-mt-20">
40
+ <CollapsibleTrigger asChild>
41
+ <button className="flex items-center gap-2 w-full group text-left">
42
+ <ChevronDown
43
+ className={cn(
44
+ "h-4 w-4 text-muted-foreground transition-transform duration-200",
45
+ !isOpen && "-rotate-90",
46
+ )}
47
+ />
48
+ <div className="flex-1 flex items-center justify-between">
49
+ <div>
50
+ <h3 className="text-sm font-semibold text-foreground group-hover:text-primary transition-colors">
51
+ {title}
52
+ </h3>
53
+ {description && (
54
+ <p className="text-xs text-muted-foreground mt-0.5">
55
+ {description}
56
+ </p>
57
+ )}
58
+ </div>
59
+ {count !== undefined && (
60
+ <span className="text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded-full">
61
+ {count}
62
+ </span>
63
+ )}
64
+ </div>
65
+ </button>
66
+ </CollapsibleTrigger>
67
+ </div>
68
+
69
+ <CollapsibleContent className="space-y-2">
70
+ <div className="ml-6 space-y-1 border-l pl-4">{children}</div>
71
+ </CollapsibleContent>
72
+ </Collapsible>
73
+ );
74
+ }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import {
5
+ Settings,
6
+ TrendingUp,
7
+ Plug,
8
+ Bell,
9
+ Shield,
10
+ Bot,
11
+ Palette,
12
+ Wrench,
13
+ } from "lucide-react";
14
+ import { cn } from "../../../../utils";
15
+ import type { SettingGroup } from "./settings-groups";
16
+
17
+ interface SettingsSidebarProps {
18
+ groups: SettingGroup[];
19
+ activeGroup: string;
20
+ onGroupChange: (groupId: string) => void;
21
+ className?: string;
22
+ }
23
+
24
+ const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
25
+ Settings,
26
+ TrendingUp,
27
+ Plug,
28
+ Bell,
29
+ Shield,
30
+ Bot,
31
+ Palette,
32
+ Wrench,
33
+ };
34
+
35
+ export function SettingsSidebar({
36
+ groups,
37
+ activeGroup,
38
+ onGroupChange,
39
+ className,
40
+ }: SettingsSidebarProps) {
41
+ return (
42
+ <nav className={cn("w-full md:w-64 shrink-0", className)}>
43
+ <div className="flex md:flex-col gap-2 overflow-x-auto md:overflow-visible pb-2 md:pb-0 snap-x custom-scrollbar">
44
+ {groups.map((group) => {
45
+ const Icon = iconMap[group.icon] || Settings;
46
+ const isActive = activeGroup === group.id;
47
+
48
+ return (
49
+ <button
50
+ key={group.id}
51
+ onClick={() => onGroupChange(group.id)}
52
+ className={cn(
53
+ "snap-start shrink-0 flex items-center gap-2 px-3 py-2 md:py-2.5 rounded-full md:rounded-lg text-left transition-all duration-200",
54
+ "bg-muted/50 md:bg-transparent hover:bg-accent/50",
55
+ isActive &&
56
+ "bg-primary text-primary-foreground md:bg-primary/10 md:text-primary font-medium md:border-l-2 md:border-transparent md:data-[active=true]:border-primary"
57
+ )}
58
+ >
59
+ <Icon
60
+ className={cn(
61
+ "h-4 w-4 shrink-0",
62
+ isActive ? "text-primary-foreground md:text-primary" : "text-muted-foreground",
63
+ )}
64
+ />
65
+ <div className="min-w-0">
66
+ <p
67
+ className={cn(
68
+ "text-sm whitespace-nowrap",
69
+ !isActive && "text-foreground/80",
70
+ )}
71
+ >
72
+ {group.label}
73
+ </p>
74
+ </div>
75
+ </button>
76
+ );
77
+ })}
78
+ </div>
79
+ </nav>
80
+ );
81
+ }