@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,27 @@
1
+ "use client";
2
+
3
+ import { memo } from "react";
4
+ import { Users, ShieldCheck, UserPlus, UserCheck } from "lucide-react";
5
+
6
+ import { CompactStatBar, CompactStatItem } from "../../ui/data-display/compact-stat-bar";
7
+
8
+ interface UserStatsProps {
9
+ stats: {
10
+ totalUsers: number;
11
+ activeUsers: number;
12
+ employees: number;
13
+ customers: number;
14
+ suppliers: number;
15
+ };
16
+ }
17
+
18
+ export const UserStats = memo(function UserStats({ stats }: UserStatsProps) {
19
+ const items: CompactStatItem[] = [
20
+ { id: "total", label: "Tổng số", value: stats.totalUsers || 0, icon: Users, colorTheme: "dark" },
21
+ { id: "employees", label: "Nhân viên", value: stats.employees || 0, icon: ShieldCheck, colorTheme: "blue" },
22
+ { id: "customers", label: "Khách hàng", value: stats.customers || 0, icon: UserPlus, colorTheme: "emerald" },
23
+ { id: "active", label: "Hoạt động", value: stats.activeUsers || 0, icon: UserCheck, colorTheme: "green" },
24
+ ];
25
+
26
+ return <CompactStatBar items={items} />;
27
+ });
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+ import { Plus, Search, LayoutGrid, List, X } from "lucide-react";
5
+ import {
6
+ Button,
7
+ Input,
8
+ Separator,
9
+ Select,
10
+ SelectContent,
11
+ SelectItem,
12
+ SelectTrigger,
13
+ SelectValue,
14
+ } from "../../ui";
15
+ import { CrudExportButton } from "../../crud/components/crud-export-button";
16
+
17
+ interface UserToolbarProps {
18
+ viewMode: "table" | "grid";
19
+ onViewModeChange: (mode: "table" | "grid") => void;
20
+ onCreateNew?: () => void;
21
+ onSearchChange: (value: string) => void;
22
+ roles?: any[];
23
+ onRoleChange?: (value: string) => void;
24
+ permissions?: { create?: boolean; export?: boolean };
25
+ userType?: string;
26
+ onUserTypeChange?: (value: string) => void;
27
+ }
28
+
29
+ export function UserToolbar({
30
+ viewMode,
31
+ onViewModeChange,
32
+ onCreateNew,
33
+ onSearchChange,
34
+ roles,
35
+ onRoleChange,
36
+ permissions = { create: true, export: true },
37
+ userType,
38
+ onUserTypeChange,
39
+ }: UserToolbarProps) {
40
+ const [localSearch, setLocalSearch] = useState("");
41
+
42
+ // Debounce search
43
+ useEffect(() => {
44
+ const timer = setTimeout(() => {
45
+ onSearchChange(localSearch);
46
+ }, 300);
47
+ return () => clearTimeout(timer);
48
+ }, [localSearch, onSearchChange]);
49
+
50
+ return (
51
+ <div className="flex items-center gap-2 flex-wrap">
52
+ <div className="relative">
53
+ <Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
54
+ <Input
55
+ placeholder="Tìm người dùng..."
56
+ value={localSearch}
57
+ onChange={(e) => setLocalSearch(e.target.value)}
58
+ className="h-9 w-[220px] lg:w-[280px] pl-9 text-xs bg-background border hover:bg-muted/50 transition-colors shadow-sm rounded-md focus-visible:ring-1 focus-visible:ring-primary"
59
+ />
60
+ {localSearch && (
61
+ <Button
62
+ variant="ghost"
63
+ size="sm"
64
+ className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 p-0 rounded-md hover:bg-muted"
65
+ onClick={() => setLocalSearch("")}
66
+ >
67
+ <X className="h-3.5 w-3.5" />
68
+ </Button>
69
+ )}
70
+ </div>
71
+
72
+ <Select value={userType} onValueChange={onUserTypeChange}>
73
+ <SelectTrigger className="h-9 w-[150px] text-xs bg-background border hover:bg-muted/50 transition-colors shadow-sm rounded-md focus-visible:ring-primary">
74
+ <SelectValue placeholder="Loại tài khoản" />
75
+ </SelectTrigger>
76
+ <SelectContent className="rounded-lg">
77
+ <SelectItem value="all">Tất cả loại</SelectItem>
78
+ <SelectItem value="employee">Nhân viên</SelectItem>
79
+ <SelectItem value="customer">Khách hàng</SelectItem>
80
+ <SelectItem value="supplier">Nhà cung cấp</SelectItem>
81
+ </SelectContent>
82
+ </Select>
83
+
84
+ <Select onValueChange={onRoleChange}>
85
+ <SelectTrigger className="h-9 w-[170px] text-xs bg-background border hover:bg-muted/50 transition-colors shadow-sm rounded-md focus-visible:ring-primary">
86
+ <SelectValue placeholder="Lọc theo vai trò" />
87
+ </SelectTrigger>
88
+ <SelectContent className="rounded-lg">
89
+ <SelectItem value="all">Tất cả vai trò</SelectItem>
90
+ {roles?.map((role) => (
91
+ <SelectItem key={role.code} value={role.code}>
92
+ {role.name}
93
+ </SelectItem>
94
+ ))}
95
+ </SelectContent>
96
+ </Select>
97
+
98
+ <div className="ml-auto flex items-center gap-2">
99
+ <div className="flex items-center bg-[#f1f5f9] dark:bg-[#181d26] rounded-[6px] p-0.5 border border-[#dddddd] dark:border-[#333840]">
100
+ <Button
101
+ variant={viewMode === "table" ? "secondary" : "ghost"}
102
+ size="sm"
103
+ className="h-8 w-8 p-0 rounded-md"
104
+ onClick={() => onViewModeChange("table")}
105
+ >
106
+ <List className="h-4 w-4" />
107
+ </Button>
108
+ <Button
109
+ variant={viewMode === "grid" ? "secondary" : "ghost"}
110
+ size="sm"
111
+ className="h-8 w-8 p-0 rounded-md"
112
+ onClick={() => onViewModeChange("grid")}
113
+ >
114
+ <LayoutGrid className="h-4 w-4" />
115
+ </Button>
116
+ </div>
117
+
118
+ {permissions.export && (
119
+ <div className="[&>button]:h-9 [&>button]:rounded-md [&>button]:shadow-sm [&>button]:bg-background [&>button]:border [&>button]:text-foreground [&>button]:hover:bg-muted/50">
120
+ <CrudExportButton
121
+ endpoint="/api/crud/users/export"
122
+ search={localSearch}
123
+ canExport={true}
124
+ />
125
+ </div>
126
+ )}
127
+
128
+ {permissions.create && (
129
+ <Button size="sm" className="h-9 px-4 text-xs bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm rounded-md font-medium border-0" onClick={onCreateNew}>
130
+ <Plus className="h-4 w-4 mr-1.5" />
131
+ Thêm mới
132
+ </Button>
133
+ )}
134
+ </div>
135
+ </div>
136
+ );
137
+ }
@@ -0,0 +1,253 @@
1
+ "use client";
2
+
3
+ import {
4
+ Card,
5
+ CardContent,
6
+ CardHeader,
7
+ Badge,
8
+ Button,
9
+ DropdownMenu,
10
+ DropdownMenuTrigger,
11
+ DropdownMenuContent,
12
+ DropdownMenuItem,
13
+ Avatar,
14
+ AvatarFallback,
15
+ AvatarImage,
16
+ } from "../../ui";
17
+ import { cn } from "../../utils";
18
+ import { Mail, Phone, MoreHorizontal, Eye, Edit, User, Briefcase, Store, ShieldCheck, Hash, Building2 } from "lucide-react";
19
+
20
+ interface UsersCardViewProps {
21
+ data: any[];
22
+ onSelect?: (user: any) => void;
23
+ }
24
+
25
+ // Professional Corporate Palette
26
+ const AVATAR_PALETTE = [
27
+ { bg: "bg-primary", text: "text-primary-foreground" }, // Primary
28
+ { bg: "bg-[#059669]", text: "text-white" }, // Emerald
29
+ { bg: "bg-[#EA580C]", text: "text-white" }, // Orange
30
+ { bg: "bg-[#7C3AED]", text: "text-white" }, // Violet
31
+ { bg: "bg-[#0891B2]", text: "text-white" }, // Cyan
32
+ ];
33
+
34
+ const ROLE_PALETTE = [
35
+ "bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800",
36
+ "bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800",
37
+ "bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-800",
38
+ "bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-900/30 dark:text-purple-400 dark:border-purple-800",
39
+ "bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-900/30 dark:text-rose-400 dark:border-rose-800",
40
+ ];
41
+
42
+ const getRoleStyle = (role: string) => {
43
+ let hash = 0;
44
+ for (let i = 0; i < role.length; i++) {
45
+ hash = role.charCodeAt(i) + ((hash << 5) - hash);
46
+ }
47
+ return ROLE_PALETTE[Math.abs(hash) % ROLE_PALETTE.length];
48
+ };
49
+
50
+ const getAvatarColor = (name: string | null) => {
51
+ if (!name) return AVATAR_PALETTE[0];
52
+ const charCode = name.charCodeAt(0) + (name.charCodeAt(name.length - 1) || 0);
53
+ return AVATAR_PALETTE[charCode % AVATAR_PALETTE.length];
54
+ };
55
+
56
+ export function UsersCardView({ data, onSelect }: UsersCardViewProps) {
57
+ if (!data.length) {
58
+ return (
59
+ <div className="flex flex-col items-center justify-center min-h-[400px] border border-[#e2e8f0] rounded-xl bg-white p-8 text-center">
60
+ <div className="size-16 rounded-full bg-primary/5 flex items-center justify-center mb-4">
61
+ <User className="size-8 text-primary" strokeWidth={1.5} />
62
+ </div>
63
+ <h3 className="text-base font-semibold text-foreground">
64
+ Không có dữ liệu
65
+ </h3>
66
+ <p className="text-sm text-muted-foreground mt-2 max-w-[300px]">
67
+ Danh sách người dùng trống. Thử thay đổi bộ lọc hoặc thêm mới.
68
+ </p>
69
+ </div>
70
+ );
71
+ }
72
+
73
+ const getInitials = (name: string | null) => {
74
+ if (!name) return "U";
75
+ return name
76
+ .split(" ")
77
+ .map((n) => n[0])
78
+ .join("")
79
+ .toUpperCase()
80
+ .slice(0, 1);
81
+ };
82
+
83
+ return (
84
+ <div className="space-y-4">
85
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 pb-4">
86
+ {data.map((user) => {
87
+ const isActive = user.isActive || user.status === "active";
88
+ const avatarColors = getAvatarColor(user.name);
89
+
90
+ return (
91
+ <Card
92
+ key={user.id}
93
+ className="group relative transition-all duration-200 border border-[#e2e8f0] bg-white dark:bg-[#0f172a] dark:border-[#334155] cursor-pointer h-full flex flex-col rounded-xl overflow-hidden shadow-sm hover:shadow-md hover:border-primary/30"
94
+ onClick={() => onSelect && onSelect(user)}
95
+ >
96
+ {/* Header */}
97
+ <div className="p-3 pb-2 flex gap-2.5 items-start bg-[#f8fafc] border-b border-[#e2e8f0] dark:bg-[#1e293b] dark:border-[#334155]">
98
+ <div className="relative shrink-0 mt-0.5">
99
+ <div
100
+ className={cn(
101
+ "size-8 rounded-full flex items-center justify-center font-semibold text-xs",
102
+ avatarColors.bg, avatarColors.text
103
+ )}
104
+ >
105
+ {user.image || user.avatar ? (
106
+ <img
107
+ src={user.image || user.avatar}
108
+ alt={user.name}
109
+ className="w-full h-full object-cover rounded-full"
110
+ />
111
+ ) : (
112
+ getInitials(user.name || user.email)
113
+ )}
114
+ </div>
115
+ {/* Status Dot */}
116
+ <div
117
+ className={cn(
118
+ "absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 border-2 border-white dark:border-[#0f172a] rounded-full z-10",
119
+ isActive ? "bg-[#10b981]" : "bg-[#cbd5e1]"
120
+ )}
121
+ title={isActive ? "Hoạt động" : "Đã khóa"}
122
+ />
123
+ </div>
124
+
125
+ <div className="flex-1 min-w-0">
126
+ <div className="flex items-center gap-2 mb-0.5">
127
+ <h3
128
+ className="font-semibold text-sm text-foreground truncate leading-tight"
129
+ title={user.name}
130
+ >
131
+ {user.name || "Chưa đặt tên"}
132
+ </h3>
133
+
134
+ {/* User Type Badge (Compact) */}
135
+ {user.userType === "customer" && (
136
+ <span className="text-[10px] px-1.5 py-0.5 font-semibold rounded bg-emerald-600 text-white shrink-0">
137
+ Khách hàng
138
+ </span>
139
+ )}
140
+ {user.userType === "supplier" && (
141
+ <span className="text-[10px] px-1.5 py-0.5 font-semibold rounded bg-orange-600 text-white shrink-0">
142
+ Nhà cung cấp
143
+ </span>
144
+ )}
145
+ {(!user.userType || user.userType === "employee") && (
146
+ <span className="text-[10px] px-1.5 py-0.5 font-semibold rounded bg-primary/10 text-primary border border-primary/20 shrink-0">
147
+ Nhân viên
148
+ </span>
149
+ )}
150
+ </div>
151
+
152
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
153
+ <span className="truncate leading-none" title={user.email}>
154
+ {user.email || "—"}
155
+ </span>
156
+ </div>
157
+ </div>
158
+
159
+ {/* Actions */}
160
+ <DropdownMenu modal={false}>
161
+ <DropdownMenuTrigger asChild>
162
+ <Button
163
+ variant="ghost"
164
+ className="h-7 w-7 p-0 text-muted-foreground hover:text-primary hover:bg-primary/5 shrink-0 -mt-1 -mr-1 rounded-full"
165
+ onClick={(e) => e.stopPropagation()}
166
+ >
167
+ <MoreHorizontal className="size-4" />
168
+ </Button>
169
+ </DropdownMenuTrigger>
170
+ <DropdownMenuContent align="end" className="w-[140px] rounded-xl border-[#e2e8f0] p-1.5">
171
+ <DropdownMenuItem
172
+ onSelect={(e) => {
173
+ e.preventDefault();
174
+ setTimeout(() => {
175
+ onSelect && onSelect(user);
176
+ }, 150);
177
+ }}
178
+ className="text-xs font-medium text-foreground cursor-pointer rounded-md py-1.5 focus:bg-primary/5 focus:text-primary"
179
+ >
180
+ <Eye className="mr-2 size-3.5" />
181
+ Xem chi tiết
182
+ </DropdownMenuItem>
183
+ <DropdownMenuItem
184
+ onSelect={(e) => {
185
+ e.preventDefault();
186
+ setTimeout(() => {
187
+ onSelect && onSelect(user);
188
+ }, 150);
189
+ }}
190
+ className="text-xs font-medium text-foreground cursor-pointer rounded-md py-1.5 focus:bg-primary/5 focus:text-primary"
191
+ >
192
+ <Edit className="mr-2 size-3.5" />
193
+ Chỉnh sửa
194
+ </DropdownMenuItem>
195
+ </DropdownMenuContent>
196
+ </DropdownMenu>
197
+ </div>
198
+
199
+ {/* Body */}
200
+ <div className="p-3 pt-0 flex-1 flex flex-col bg-white dark:bg-[#0f172a]">
201
+
202
+ {/* 2-Column Compact Grid */}
203
+ <div className="grid grid-cols-2 gap-x-2 gap-y-1.5 mb-2 mt-2">
204
+ <div className="flex items-center gap-1.5 min-w-0" title="Số điện thoại">
205
+ <Phone className="size-3 text-muted-foreground shrink-0" strokeWidth={1.5} />
206
+ <span className="text-xs text-muted-foreground truncate">
207
+ {user.profiles?.phone || user.phone || "—"}
208
+ </span>
209
+ </div>
210
+
211
+ <div className="flex items-center gap-1.5 min-w-0" title="Trạng thái">
212
+ <ShieldCheck className="size-3 text-muted-foreground shrink-0" strokeWidth={1.5} />
213
+ <span className={cn("text-xs font-medium truncate", isActive ? "text-emerald-600 dark:text-emerald-500" : "text-muted-foreground")}>
214
+ {isActive ? "Đang hoạt động" : "Đã khóa"}
215
+ </span>
216
+ </div>
217
+
218
+ <div className="flex items-center gap-1.5 min-w-0" title="Bộ phận">
219
+ <Briefcase className="size-3 text-muted-foreground shrink-0" strokeWidth={1.5} />
220
+ <span className="text-xs text-muted-foreground truncate">
221
+ {user.departmentName || "—"}
222
+ </span>
223
+ </div>
224
+
225
+ <div className="flex items-center gap-1.5 min-w-0" title="Chi nhánh">
226
+ <Building2 className="size-3 text-muted-foreground shrink-0" strokeWidth={1.5} />
227
+ <span className="text-xs text-muted-foreground truncate">
228
+ {(user.branchNames && user.branchNames.length > 0) ? user.branchNames.join(", ") : (user.branchName || user.branch?.name || "—")}
229
+ </span>
230
+ </div>
231
+ </div>
232
+
233
+ {/* Roles - Compact Format */}
234
+ {user.roleNames && user.roleNames.length > 0 && (
235
+ <div className="mt-auto pt-2.5 border-t border-[#e2e8f0] dark:border-[#334155] flex flex-wrap gap-1">
236
+ {user.roleNames.map((role: string, i: number) => {
237
+ const style = getRoleStyle(role);
238
+ return (
239
+ <span key={i} className={cn("text-[10px] px-1.5 py-0.5 font-medium rounded truncate max-w-[100px] inline-flex items-center border", style)}>
240
+ {role}
241
+ </span>
242
+ );
243
+ })}
244
+ </div>
245
+ )}
246
+ </div>
247
+ </Card>
248
+ );
249
+ })}
250
+ </div>
251
+ </div>
252
+ );
253
+ }
@@ -0,0 +1,11 @@
1
+ export * from "./types";
2
+ export * from "./schemas";
3
+ export * from "./profile-page";
4
+ // Export sub-components if needed for granular usage
5
+ export * from "./components/profile-info";
6
+ export * from "./components/dangerous-zone";
7
+ export * from "./components/profile-info-form";
8
+ export * from "./components/delete-account-form";
9
+ // Client page components cause dts build failures - import directly from source
10
+ // export * from "./pages/users-client-page";
11
+ export * from "./user-service";
@@ -0,0 +1,234 @@
1
+ "use client";
2
+
3
+ import { Plus } from "lucide-react";
4
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
5
+ import * as React from "react";
6
+
7
+ import type { EntityConfig, CrudPermissions } from "../../types";
8
+
9
+ import { Button } from "../../ui";
10
+ import {
11
+ Card,
12
+ CardContent,
13
+ CardDescription,
14
+ CardHeader,
15
+ CardTitle,
16
+ } from "../../ui";
17
+ import {
18
+ Select,
19
+ SelectContent,
20
+ SelectItem,
21
+ SelectTrigger,
22
+ SelectValue,
23
+ } from "../../ui";
24
+ import { Separator } from "../../ui";
25
+ import { CrudProvider } from "../../crud/components/crud-provider";
26
+ import { CrudTable } from "../../crud/components/crud-table";
27
+ import { CrudExportButton } from "../../crud/components/crud-export-button";
28
+
29
+ interface User {
30
+ id: string;
31
+ name: string | null;
32
+ email: string;
33
+ image: string | null;
34
+ status: string;
35
+ roleNames: string | null;
36
+ departmentName: string | null;
37
+ jobTitleName: string | null;
38
+ createdAt: Date;
39
+ }
40
+
41
+ interface UserStats {
42
+ totalUsers: number;
43
+ activeUsers: number;
44
+ inactiveUsers: number;
45
+ totalRoles: number;
46
+ }
47
+
48
+ interface UserListPageProps {
49
+ initialData: {
50
+ data: User[];
51
+ page: number;
52
+ pageSize: number;
53
+ total: number;
54
+ };
55
+ config: EntityConfig;
56
+ permissions: CrudPermissions;
57
+ dictionary: any;
58
+ roles: any[];
59
+ stats: UserStats;
60
+ }
61
+
62
+ function UserListPageContent({
63
+ dictionary,
64
+ config,
65
+ stats,
66
+ initialData,
67
+ searchParams,
68
+ roles,
69
+ handleRoleChange,
70
+ handleTypeChange,
71
+ }: any) {
72
+ return (
73
+ <div className="flex h-full flex-col bg-background">
74
+ {/* Editorial Signature Banner */}
75
+ <div className="p-6 pb-2">
76
+ <div className="p-8 rounded-[16px] bg-slate-900 text-slate-50 shadow-sm relative overflow-hidden">
77
+ {/* Subtle decoration to emulate Notion's dots or Airtable's color blocks */}
78
+ <div className="absolute top-0 right-0 -translate-y-12 translate-x-1/3 w-96 h-96 bg-primary/20 rounded-full blur-3xl opacity-50 pointer-events-none" />
79
+
80
+ <div className="relative z-10 flex flex-col xl:flex-row xl:items-end justify-between gap-8">
81
+ <div className="max-w-2xl">
82
+ <h1 className="text-3xl font-semibold mb-2 tracking-tight text-white">{config.name}</h1>
83
+ <p className="text-slate-400 text-sm leading-relaxed max-w-lg">{config.description}</p>
84
+ </div>
85
+
86
+ <div className="flex flex-wrap items-center gap-x-8 gap-y-6 text-sm">
87
+ <div className="flex flex-col">
88
+ <span className="text-slate-400 mb-1 font-medium tracking-wide text-[11px] uppercase">{dictionary.users?.stats?.totalUsers || "Tổng Users"}</span>
89
+ <span className="text-3xl font-semibold tracking-tight text-white leading-none">{stats.totalUsers}</span>
90
+ </div>
91
+ <div className="hidden sm:block w-px h-10 bg-slate-800" />
92
+ <div className="flex flex-col">
93
+ <span className="text-slate-400 mb-1 font-medium tracking-wide text-[11px] uppercase flex items-center gap-1.5">
94
+ <span className="w-2 h-2 rounded-full bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.5)]"></span>
95
+ {dictionary.users?.stats?.activeUsers || "Hoạt động"}
96
+ </span>
97
+ <span className="text-3xl font-semibold tracking-tight text-white leading-none">{stats.activeUsers}</span>
98
+ </div>
99
+ <div className="hidden sm:block w-px h-10 bg-slate-800" />
100
+ <div className="flex flex-col">
101
+ <span className="text-slate-400 mb-1 font-medium tracking-wide text-[11px] uppercase flex items-center gap-1.5">
102
+ <span className="w-2 h-2 rounded-full bg-rose-400 shadow-[0_0_8px_rgba(251,113,133,0.5)]"></span>
103
+ {dictionary.users?.stats?.inactiveUsers || "Tạm ngưng"}
104
+ </span>
105
+ <span className="text-3xl font-semibold tracking-tight text-white leading-none">{stats.inactiveUsers}</span>
106
+ </div>
107
+ <div className="hidden sm:block w-px h-10 bg-slate-800" />
108
+ <div className="flex flex-col">
109
+ <span className="text-slate-400 mb-1 font-medium tracking-wide text-[11px] uppercase">{dictionary.users?.stats?.totalRoles || "Vai trò"}</span>
110
+ <span className="text-3xl font-semibold tracking-tight text-white leading-none">{stats.totalRoles}</span>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+
117
+ <div className="flex-1 overflow-auto px-6 py-4">
118
+ <div className="flex flex-col gap-4">
119
+ {/* Action & Filter Rail */}
120
+ <div className="flex flex-col sm:flex-row items-center justify-between gap-4 bg-muted/20 p-2 rounded-xl border border-border/50">
121
+ <div className="flex flex-wrap items-center gap-2 w-full sm:w-auto">
122
+ <Select
123
+ value={searchParams.get("role") || "all"}
124
+ onValueChange={handleRoleChange}
125
+ >
126
+ <SelectTrigger className="w-[180px] bg-background border-transparent hover:border-border/50 transition-colors rounded-lg shadow-sm">
127
+ <SelectValue
128
+ placeholder={
129
+ dictionary.users?.filters?.filterByRole || "Lọc theo vai trò"
130
+ }
131
+ />
132
+ </SelectTrigger>
133
+ <SelectContent>
134
+ <SelectItem value="all">
135
+ {dictionary.common?.all || "Tất cả vai trò"}
136
+ </SelectItem>
137
+ {roles.map((role: any) => (
138
+ <SelectItem key={role.code} value={role.code}>
139
+ {role.name}
140
+ </SelectItem>
141
+ ))}
142
+ </SelectContent>
143
+ </Select>
144
+
145
+ <Select
146
+ value={searchParams.get("type") || "all"}
147
+ onValueChange={handleTypeChange}
148
+ >
149
+ <SelectTrigger className="w-[180px] bg-background border-transparent hover:border-border/50 transition-colors rounded-lg shadow-sm">
150
+ <SelectValue
151
+ placeholder={
152
+ dictionary.users?.filters?.filterByType || "Lọc theo loại"
153
+ }
154
+ />
155
+ </SelectTrigger>
156
+ <SelectContent>
157
+ <SelectItem value="all">
158
+ {dictionary.common?.all || "Tất cả loại"}
159
+ </SelectItem>
160
+ <SelectItem value="internal">
161
+ {dictionary.users?.types?.internal || "Nội bộ (Internal)"}
162
+ </SelectItem>
163
+ <SelectItem value="external">
164
+ {dictionary.users?.types?.external || "Bên ngoài (External)"}
165
+ </SelectItem>
166
+ </SelectContent>
167
+ </Select>
168
+ </div>
169
+
170
+ <div className="flex items-center gap-2 w-full sm:w-auto shrink-0 pr-1">
171
+ <CrudExportButton endpoint={config.apiEndpoint} />
172
+ </div>
173
+ </div>
174
+
175
+ <CrudTable data={initialData} />
176
+ </div>
177
+ </div>
178
+ </div>
179
+ );
180
+ }
181
+
182
+ export function UserListPage({
183
+ initialData,
184
+ config,
185
+ permissions,
186
+ dictionary,
187
+ roles,
188
+ stats,
189
+ }: UserListPageProps) {
190
+ const router = useRouter();
191
+ const pathname = usePathname();
192
+ const searchParams = useSearchParams();
193
+
194
+ const handleRoleChange = (role: string) => {
195
+ const params = new URLSearchParams(searchParams);
196
+ if (role && role !== "all") {
197
+ params.set("role", role);
198
+ } else {
199
+ params.delete("role");
200
+ }
201
+ params.set("page", "1");
202
+ router.push(`${pathname}?${params.toString()}`);
203
+ };
204
+
205
+ const handleTypeChange = (type: string) => {
206
+ const params = new URLSearchParams(searchParams);
207
+ if (type && type !== "all") {
208
+ params.set("type", type);
209
+ } else {
210
+ params.delete("type");
211
+ }
212
+ params.set("page", "1");
213
+ router.push(`${pathname}?${params.toString()}`);
214
+ };
215
+
216
+ return (
217
+ <CrudProvider
218
+ initialConfig={config}
219
+ initialPermissions={permissions}
220
+ initialTranslations={dictionary}
221
+ >
222
+ <UserListPageContent
223
+ dictionary={dictionary}
224
+ config={config}
225
+ stats={stats}
226
+ initialData={initialData}
227
+ searchParams={searchParams}
228
+ roles={roles}
229
+ handleRoleChange={handleRoleChange}
230
+ handleTypeChange={handleTypeChange}
231
+ />
232
+ </CrudProvider>
233
+ );
234
+ }