@gadmin2n/schematics 0.0.63 → 0.0.65

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 (223) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +40 -26
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +30 -4
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/example.prisma +33 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +163 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +70 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Game.ts +6 -6
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/ITActivityDay.ts +70 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Log.ts +2 -2
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Role.ts +2 -2
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env +20 -9
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env.local +1 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.eslintrc.js +1 -0
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.prettierignore +1 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/README.md +2 -18
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/gadmin-cli.json +1 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/migrate-between-pg-schemas.js +1232 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +65 -38
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma/.generator.prisma +4 -3
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +16 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/games.ts +1 -71
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +17 -21
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +278 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/seedDataMngtPages.ts +258 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/users.ts +7 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/alias.config.ts +7 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.controller.ts +151 -11
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.module.ts +29 -13
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.service.ts +151 -12
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/auth.guard.ts +87 -41
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/http-cache.interceptor.ts +21 -0
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/logger.ts +19 -0
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/safe-log.util.ts +176 -0
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/{yufuid.ts → taihu.ts} +49 -34
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/tracing.ts +174 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/trim.pipe.ts +51 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/utils.ts +91 -0
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/woaAuth.ts +25 -12
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +22 -12
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +20 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.ts +190 -0
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.module.ts +10 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +338 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.ts +83 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +20 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.ts +188 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.module.ts +10 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +18 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.ts +83 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +20 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +250 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +10 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +18 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.ts +1051 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +20 -0
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.ts +196 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.module.ts +13 -0
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +18 -0
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.ts +219 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +20 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +196 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +10 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +18 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.ts +199 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +20 -0
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +210 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +12 -0
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +18 -0
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.ts +849 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/roles-refresher.service.ts +133 -0
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +20 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.ts +196 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.module.ts +10 -0
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +18 -0
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.ts +201 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +20 -0
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.ts +196 -0
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.module.ts +10 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +18 -0
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.ts +216 -0
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +20 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +198 -0
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +10 -0
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +18 -0
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +104 -0
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +130 -0
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/server/tsconfig.json +18 -3
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/index.html +19 -0
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +34 -42
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/postcss.config.cjs +6 -0
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/App.tsx +111 -185
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/auditLogProvider.ts +5 -5
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/color-mode/index.tsx +49 -51
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +38 -0
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +4 -0
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/{header/index.tsx → header.tsx} +22 -31
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/index.ts +3 -1
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx +32 -0
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx +19 -0
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +331 -166
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +61 -0
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/pagination-total.tsx +21 -0
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/index.ts +1 -0
  104. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/role-tag.tsx +44 -0
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/text.tsx +74 -0
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +258 -0
  107. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +16 -0
  108. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/audit-log.enum.ts +13 -0
  109. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/index.ts +1 -0
  110. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +2 -0
  111. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -4
  112. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +211 -0
  113. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useFetchData.ts +33 -0
  114. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useRoles.ts +30 -0
  115. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useUserPageAccess.ts +339 -0
  116. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +8 -4
  117. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/index.tsx +3 -11
  118. package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/en/common.json +1 -1
  119. package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/zh_CN/common.json +1 -1
  120. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.css +3 -0
  121. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +134 -0
  122. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/create.tsx +113 -0
  123. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/edit.tsx +122 -0
  124. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.ts +8 -0
  125. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.tsx +6 -0
  126. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/list.tsx +213 -0
  127. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/show.tsx +61 -0
  128. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +168 -0
  129. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/CreatePageModal.tsx +42 -0
  130. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/EditPageModal.tsx +42 -0
  131. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +101 -0
  132. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +731 -0
  133. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/create.tsx +113 -0
  134. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/edit.tsx +122 -0
  135. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +36 -0
  136. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +8 -0
  137. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.tsx +6 -0
  138. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +1113 -0
  139. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/queries.ts +17 -0
  140. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/show.tsx +61 -0
  141. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +44 -0
  142. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/create.tsx +113 -0
  143. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/edit.tsx +122 -0
  144. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/index.tsx +6 -0
  145. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/list.tsx +243 -0
  146. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/show.tsx +61 -0
  147. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permission-readme/index.tsx +1088 -0
  148. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/CreateModal.tsx +25 -0
  149. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/EditModal.tsx +28 -0
  150. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +160 -0
  151. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/modal.tsx +202 -0
  152. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/create.tsx +113 -0
  153. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/edit.tsx +122 -0
  154. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +9 -0
  155. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.tsx +6 -0
  156. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +184 -0
  157. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/queries.ts +10 -0
  158. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/show.tsx +61 -0
  159. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/types.ts +9 -0
  160. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/CreateModal.tsx +30 -0
  161. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/EditModal.tsx +47 -0
  162. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +56 -0
  163. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +302 -0
  164. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/create.tsx +113 -0
  165. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/edit.tsx +122 -0
  166. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +35 -0
  167. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +8 -0
  168. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.tsx +6 -0
  169. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +382 -0
  170. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/queries.ts +8 -0
  171. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/show.tsx +61 -0
  172. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/types.ts +8 -0
  173. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/create.tsx +113 -0
  174. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/edit.tsx +122 -0
  175. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/index.tsx +6 -0
  176. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/list.tsx +243 -0
  177. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/show.tsx +61 -0
  178. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/create.tsx +113 -0
  179. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/edit.tsx +122 -0
  180. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/index.tsx +6 -0
  181. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/list.tsx +243 -0
  182. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/show.tsx +61 -0
  183. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/create-modal.tsx +17 -0
  184. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/edit-modal.tsx +19 -0
  185. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +188 -0
  186. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/index.ts +5 -0
  187. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/role-tag.tsx +48 -0
  188. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +140 -0
  189. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/create.tsx +113 -0
  190. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/edit.tsx +122 -0
  191. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +8 -0
  192. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.tsx +6 -0
  193. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +342 -0
  194. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/queries.ts +14 -0
  195. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/show.tsx +61 -0
  196. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +132 -0
  197. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/fc.css +58 -0
  198. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +128 -0
  199. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-drawer.module.css +18 -0
  200. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-page.module.css +21 -0
  201. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/audit-log.ts +1 -0
  202. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/index.ts +3 -0
  203. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/role.ts +7 -0
  204. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/user.ts +1 -0
  205. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-name-initials.ts +8 -0
  206. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-random-color.ts +27 -0
  207. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/index.ts +2 -0
  208. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/utils.tsx +5 -0
  209. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/vite-env.d.ts +1 -0
  210. package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +3 -3
  211. package/dist/lib/application/files/gadmin2-game-angle-demo/web/vite.config.ts +31 -0
  212. package/package.json +1 -1
  213. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/sample.prisma +0 -65
  214. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Source.ts +0 -76
  215. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Tasklog.ts +0 -76
  216. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/roles.ts +0 -4
  217. package/dist/lib/application/files/gadmin2-game-angle-demo/web/craco.config.js +0 -27
  218. package/dist/lib/application/files/gadmin2-game-angle-demo/web/public/index.html +0 -53
  219. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/styles.ts +0 -10
  220. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/react-app-env.d.ts +0 -1
  221. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/reportWebVitals.ts +0 -15
  222. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.less +0 -79
  223. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/VanillaJSONEditor/{index.js → index.jsx} +0 -0
@@ -0,0 +1,1113 @@
1
+ import React, { useCallback, useMemo, useRef, useState } from "react";
2
+ import { DeleteButton, EditButton, useTable } from "@refinedev/antd";
3
+ import { Table, Button, Space, Tooltip, Tag, Alert, Modal, message, Tabs } from "antd";
4
+ import {
5
+ SaveOutlined,
6
+ UndoOutlined,
7
+ HolderOutlined,
8
+ RightOutlined,
9
+ DownOutlined,
10
+ PlusOutlined,
11
+ EyeOutlined,
12
+ TeamOutlined,
13
+ } from "@ant-design/icons";
14
+ import {
15
+ DndContext,
16
+ closestCenter,
17
+ KeyboardSensor,
18
+ PointerSensor,
19
+ useSensor,
20
+ useSensors,
21
+ DragEndEvent,
22
+ } from "@dnd-kit/core";
23
+ import {
24
+ SortableContext,
25
+ sortableKeyboardCoordinates,
26
+ useSortable,
27
+ verticalListSortingStrategy,
28
+ } from "@dnd-kit/sortable";
29
+ import { CSS } from "@dnd-kit/utilities";
30
+ import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
31
+
32
+ import { customRequest } from "helpers/login";
33
+
34
+ import { AssignRolesModal } from "./Components/AssignRolesModal";
35
+ import { EditPageModal } from "./Components/EditPageModal";
36
+ import { CreatePageModal } from "./Components/CreatePageModal";
37
+ import { PageDetailDrawer } from "./Components/PageDetailDrawer";
38
+ import type {
39
+ PageMenuTreeNode,
40
+ PageResourceAssignment,
41
+ PageResourcePermissionSummary,
42
+ ResourceDefinition,
43
+ } from "./types";
44
+ import { usePageManagement } from "./hooks/usePageManagement";
45
+ import { useTranslation } from "react-i18next";
46
+
47
+ const RESOURCE_NAME_DISPLAY_TRUNCATION_LIMIT = 40;
48
+
49
+ // Sparse number gap for sortOrder (allows inserting items between existing ones)
50
+ const SORT_ORDER_GAP = 10000;
51
+
52
+ // API function for updating page order (sortOrder and parentId)
53
+ const updatePageOrder = async (
54
+ pageId: number,
55
+ updateData: { sortOrder?: number; parentId?: number | null },
56
+ ): Promise<any> => {
57
+ return customRequest(`page/updateUnique/${pageId}`, "PATCH", updateData);
58
+ };
59
+
60
+ const safelyConvertToNumber = (value: unknown): number | null => {
61
+ if (typeof value === "number" && !Number.isNaN(value)) {
62
+ return value;
63
+ }
64
+ if (typeof value === "string" && value.trim().length > 0) {
65
+ const parsedNumericValue = Number(value);
66
+ return Number.isNaN(parsedNumericValue) ? null : parsedNumericValue;
67
+ }
68
+ return null;
69
+ };
70
+
71
+ const extractPermissionActionsFromAssociation = (
72
+ associationRecord: any,
73
+ resourceRecord: any,
74
+ ): string[] => {
75
+ const aggregatedPermissionActions = new Set<string>();
76
+
77
+ const associationActions =
78
+ associationRecord?.actions ?? associationRecord?.actionList ?? associationRecord?.permissions;
79
+
80
+ if (Array.isArray(associationActions)) {
81
+ associationActions.forEach((action: any) => {
82
+ if (typeof action === "string" && action) {
83
+ aggregatedPermissionActions.add(action);
84
+ } else if (typeof action === "object" && action?.name) {
85
+ aggregatedPermissionActions.add(action.name);
86
+ } else if (typeof action === "object" && action?.code) {
87
+ aggregatedPermissionActions.add(action.code);
88
+ }
89
+ });
90
+ } else if (typeof associationActions === "string") {
91
+ associationActions
92
+ .split(",")
93
+ .map((action: string) => action.trim())
94
+ .filter(Boolean)
95
+ .forEach(action => aggregatedPermissionActions.add(action));
96
+ }
97
+
98
+ return Array.from(aggregatedPermissionActions);
99
+ };
100
+
101
+ // Draggable row component for dnd-kit
102
+ interface DraggableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
103
+ "data-row-key": string;
104
+ }
105
+
106
+ const DraggableRow: React.FC<DraggableRowProps> = ({ children, ...props }) => {
107
+ const id = props["data-row-key"];
108
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
109
+ id,
110
+ });
111
+
112
+ const style: React.CSSProperties = {
113
+ ...props.style,
114
+ transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
115
+ transition,
116
+ ...(isDragging
117
+ ? {
118
+ position: "relative",
119
+ zIndex: 9999,
120
+ background: "#e6f4ff",
121
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
122
+ borderLeft: "3px solid #1677ff",
123
+ opacity: 0.9,
124
+ }
125
+ : {}),
126
+ };
127
+
128
+ return (
129
+ <tr {...props} ref={setNodeRef} style={style} {...attributes}>
130
+ {React.Children.map(children, child => {
131
+ if ((child as React.ReactElement).key === "drag-handle") {
132
+ return React.cloneElement(child as React.ReactElement, {
133
+ children: (
134
+ <HolderOutlined
135
+ {...listeners}
136
+ style={{ cursor: "grab", color: "#999", touchAction: "none" }}
137
+ />
138
+ ),
139
+ });
140
+ }
141
+ return child;
142
+ })}
143
+ </tr>
144
+ );
145
+ };
146
+
147
+ export const PageManagementListPage: React.FC = () => {
148
+ const { i18n } = useTranslation();
149
+ const { tableProps } = useTable({
150
+ resource: "page",
151
+ pagination: {
152
+ pageSize: 20,
153
+ },
154
+ });
155
+
156
+ const {
157
+ pageListData,
158
+ refetchPageList,
159
+ pageListRequestQuery,
160
+ availableResourcesData,
161
+ refetchAvailableResources,
162
+ resourceListRequestQuery,
163
+ pageResourceAssociationsData,
164
+ refetchPageResourceAssociations,
165
+ pageResourceAssociationRequestQuery,
166
+ } = usePageManagement();
167
+
168
+ const resourceLookupByIdMap = useMemo<Record<number, ResourceDefinition>>(() => {
169
+ const resourcesArray: any[] = Array.isArray(availableResourcesData?.data)
170
+ ? availableResourcesData.data
171
+ : [];
172
+ const lookupMap: Record<number, ResourceDefinition> = {};
173
+
174
+ resourcesArray.forEach((resource: any) => {
175
+ const resourceId = safelyConvertToNumber(resource?.id);
176
+ if (resourceId !== null) {
177
+ lookupMap[resourceId] = {
178
+ id: resourceId,
179
+ name: resource?.name || resource?.code || `Resource ${resourceId}`,
180
+ description: resource?.description,
181
+ type: resource?.type,
182
+ };
183
+ }
184
+ });
185
+
186
+ return lookupMap;
187
+ }, [availableResourcesData]);
188
+
189
+ const availableResourcesListForModal = useMemo<ResourceDefinition[]>(() => {
190
+ return Object.values(resourceLookupByIdMap);
191
+ }, [resourceLookupByIdMap]);
192
+
193
+ const pageMenuTreeOptionsData = useMemo<PageMenuTreeNode[]>(() => {
194
+ const pagesArray: any[] = Array.isArray(pageListData?.data) ? pageListData.data : [];
195
+ const nodeMapByPageId = new Map<
196
+ number,
197
+ { treeNode: PageMenuTreeNode; parentPageId: number | null }
198
+ >();
199
+
200
+ pagesArray.forEach((page: any) => {
201
+ const pageId = safelyConvertToNumber(page?.id ?? page?.pageId ?? page?.page_id);
202
+ if (pageId === null) {
203
+ return;
204
+ }
205
+
206
+ const parentPageId =
207
+ safelyConvertToNumber(page?.parentId ?? page?.parent_id ?? page?.parent?.id) ?? null;
208
+ const pageDisplayTitle = page?.name || page?.code || `Page ${pageId}`;
209
+
210
+ nodeMapByPageId.set(pageId, {
211
+ treeNode: {
212
+ title: pageDisplayTitle,
213
+ value: String(pageId),
214
+ key: String(pageId),
215
+ children: [],
216
+ },
217
+ parentPageId,
218
+ });
219
+ });
220
+
221
+ const rootLevelNodes: PageMenuTreeNode[] = [];
222
+
223
+ nodeMapByPageId.forEach(({ treeNode, parentPageId }) => {
224
+ if (parentPageId !== null && nodeMapByPageId.has(parentPageId)) {
225
+ nodeMapByPageId.get(parentPageId)?.treeNode.children?.push(treeNode);
226
+ } else {
227
+ rootLevelNodes.push(treeNode);
228
+ }
229
+ });
230
+
231
+ const sortTreeNodesAlphabetically = (nodes: PageMenuTreeNode[]) => {
232
+ nodes.sort((nodeA, nodeB) => {
233
+ const titleA = typeof nodeA.title === "string" ? nodeA.title : String(nodeA.key);
234
+ const titleB = typeof nodeB.title === "string" ? nodeB.title : String(nodeB.key);
235
+ return titleA.localeCompare(titleB);
236
+ });
237
+ nodes.forEach(node => {
238
+ if (node.children && node.children.length) {
239
+ sortTreeNodesAlphabetically(node.children);
240
+ }
241
+ });
242
+ };
243
+
244
+ const removeEmptyChildrenArrays = (nodes: PageMenuTreeNode[]): PageMenuTreeNode[] =>
245
+ nodes.map(node => {
246
+ if (node.children && node.children.length) {
247
+ return {
248
+ ...node,
249
+ children: removeEmptyChildrenArrays(node.children),
250
+ };
251
+ }
252
+ const { children, ...nodeWithoutChildren } = node;
253
+ return nodeWithoutChildren as PageMenuTreeNode;
254
+ });
255
+
256
+ sortTreeNodesAlphabetically(rootLevelNodes);
257
+ return removeEmptyChildrenArrays(rootLevelNodes);
258
+ }, [pageListData]);
259
+
260
+ const pageResourcePermissionSummaryMap = useMemo<
261
+ Record<number, PageResourcePermissionSummary>
262
+ >(() => {
263
+ const associationsArray: any[] = Array.isArray(pageResourceAssociationsData?.data)
264
+ ? pageResourceAssociationsData.data
265
+ : [];
266
+ const summaryMapByPageId: Record<number, PageResourcePermissionSummary> = {};
267
+
268
+ associationsArray.forEach((association: any) => {
269
+ const pageId = safelyConvertToNumber(association?.pageId);
270
+ const resourceId = safelyConvertToNumber(association?.resourceId);
271
+
272
+ if (pageId === null || resourceId === null) {
273
+ return;
274
+ }
275
+
276
+ if (!summaryMapByPageId[pageId]) {
277
+ summaryMapByPageId[pageId] = {
278
+ resourceIds: [],
279
+ resourceNames: [],
280
+ allPermissionActions: [],
281
+ };
282
+ }
283
+
284
+ const pageSummary = summaryMapByPageId[pageId];
285
+
286
+ if (!pageSummary.resourceIds.includes(resourceId)) {
287
+ pageSummary.resourceIds.push(resourceId);
288
+ }
289
+
290
+ const resourceRecord = association?.resource ?? resourceLookupByIdMap[resourceId];
291
+ const resourceDisplayName =
292
+ resourceRecord?.name || resourceRecord?.code || `Resource ${resourceId}`;
293
+
294
+ if (resourceDisplayName && !pageSummary.resourceNames.includes(resourceDisplayName)) {
295
+ pageSummary.resourceNames.push(resourceDisplayName);
296
+ }
297
+
298
+ extractPermissionActionsFromAssociation(association, resourceRecord).forEach(
299
+ permissionAction => {
300
+ if (permissionAction && !pageSummary.allPermissionActions.includes(permissionAction)) {
301
+ pageSummary.allPermissionActions.push(permissionAction);
302
+ }
303
+ },
304
+ );
305
+ });
306
+
307
+ return summaryMapByPageId;
308
+ }, [pageResourceAssociationsData, resourceLookupByIdMap]);
309
+
310
+ const getPageResourceAssignmentsForModal = useCallback(
311
+ (pageId: number): PageResourceAssignment[] => {
312
+ console.log(
313
+ "Getting resource assignments for page ID:",
314
+ pageId,
315
+ pageResourceAssociationsData,
316
+ );
317
+ const associationsArray: any[] = Array.isArray(pageResourceAssociationsData?.data)
318
+ ? pageResourceAssociationsData.data
319
+ : [];
320
+
321
+ const pageAssignments: PageResourceAssignment[] = [];
322
+
323
+ associationsArray.forEach((association: any) => {
324
+ const associationPageId = safelyConvertToNumber(association?.pageId);
325
+ const resourceId = safelyConvertToNumber(association?.resourceId);
326
+
327
+ if (associationPageId !== pageId || resourceId === null) {
328
+ return;
329
+ }
330
+
331
+ const resourceRecord = association?.resource ?? resourceLookupByIdMap[resourceId];
332
+ const resourceDisplayName =
333
+ resourceRecord?.name || resourceRecord?.code || `Resource ${resourceId}`;
334
+
335
+ pageAssignments.push({
336
+ resourceId,
337
+ resourceName: resourceDisplayName,
338
+ assignedPermissions: extractPermissionActionsFromAssociation(association, resourceRecord),
339
+ });
340
+ });
341
+
342
+ return pageAssignments;
343
+ },
344
+ [pageResourceAssociationsData, resourceLookupByIdMap],
345
+ );
346
+
347
+ const [activeModalState, setActiveModalState] = useState<{
348
+ modalType: "view" | "edit" | "create" | null;
349
+ targetPageId: number | null;
350
+ defaultParentId?: number | null;
351
+ defaultPathPrefix?: string;
352
+ }>({
353
+ modalType: null,
354
+ targetPageId: null,
355
+ defaultParentId: null,
356
+ defaultPathPrefix: "",
357
+ });
358
+
359
+ // State for draggable table
360
+ const [pendingChanges, setPendingChanges] = useState<
361
+ Map<number, { sortOrder?: number; parentId?: number | null }>
362
+ >(new Map());
363
+ const [isSavingOrder, setIsSavingOrder] = useState(false);
364
+
365
+ // State for tree expand/collapse
366
+ const [expandedKeys, setExpandedKeys] = useState<Set<number>>(new Set());
367
+
368
+ // State for detail drawer
369
+ const [drawerPage, setDrawerPage] = useState<any>(null);
370
+
371
+ // State for assign roles modal
372
+ const [assignRolesTarget, setAssignRolesTarget] = useState<{
373
+ pageId: number;
374
+ pageName: string;
375
+ } | null>(null);
376
+
377
+ // State for active tab
378
+ const [activeTab, setActiveTab] = useState<string>("regular");
379
+
380
+ // Ref for expand button click/double-click disambiguation
381
+ const expandClickTimer = useRef<number | null>(null);
382
+
383
+ const refreshAllPageRelatedData = useCallback(() => {
384
+ refetchPageList(pageListRequestQuery ?? {});
385
+ refetchAvailableResources(resourceListRequestQuery ?? {});
386
+ refetchPageResourceAssociations(pageResourceAssociationRequestQuery ?? {});
387
+ }, [
388
+ refetchPageList,
389
+ pageListRequestQuery,
390
+ refetchAvailableResources,
391
+ resourceListRequestQuery,
392
+ refetchPageResourceAssociations,
393
+ pageResourceAssociationRequestQuery,
394
+ ]);
395
+
396
+ const handleModalClose = useCallback(() => {
397
+ setActiveModalState({
398
+ modalType: null,
399
+ targetPageId: null,
400
+ defaultParentId: null,
401
+ defaultPathPrefix: "",
402
+ });
403
+ }, []);
404
+
405
+ const handleModalOperationSuccess = useCallback(() => {
406
+ handleModalClose();
407
+ refreshAllPageRelatedData();
408
+ }, [refreshAllPageRelatedData, handleModalClose]);
409
+
410
+ // Calculate new sortOrder when dropping a node
411
+ const calculateNewSortOrder = useCallback(
412
+ (siblings: any[], dropIndex: number, dragNodeId: number): number => {
413
+ // Filter out the dragged node from siblings
414
+ const filteredSiblings = siblings.filter(s => safelyConvertToNumber(s.id) !== dragNodeId);
415
+
416
+ if (filteredSiblings.length === 0) {
417
+ // First item in this parent
418
+ return SORT_ORDER_GAP;
419
+ }
420
+
421
+ if (dropIndex === 0) {
422
+ // Dropped at the beginning
423
+ const firstSibling = filteredSiblings[0];
424
+ const firstOrder = firstSibling?.sortOrder ?? SORT_ORDER_GAP;
425
+ return Math.max(1, Math.floor(firstOrder / 2));
426
+ }
427
+
428
+ if (dropIndex >= filteredSiblings.length) {
429
+ // Dropped at the end
430
+ const lastSibling = filteredSiblings[filteredSiblings.length - 1];
431
+ const lastOrder = lastSibling?.sortOrder ?? 0;
432
+ return lastOrder + SORT_ORDER_GAP;
433
+ }
434
+
435
+ // Dropped in the middle
436
+ const prevSibling = filteredSiblings[dropIndex - 1];
437
+ const nextSibling = filteredSiblings[dropIndex];
438
+ const prevOrder = prevSibling?.sortOrder ?? 0;
439
+ const nextOrder = nextSibling?.sortOrder ?? prevOrder + SORT_ORDER_GAP * 2;
440
+
441
+ // Calculate middle value
442
+ const newOrder = Math.floor((prevOrder + nextOrder) / 2);
443
+
444
+ // If there's no room (gap too small), we need to redistribute
445
+ if (newOrder <= prevOrder || newOrder >= nextOrder) {
446
+ // Return a value that signals we need redistribution
447
+ // For now, just use prevOrder + 1 and let batch save handle redistribution
448
+ return prevOrder + 1;
449
+ }
450
+
451
+ return newOrder;
452
+ },
453
+ [],
454
+ );
455
+
456
+ // Toggle expand/collapse for a node
457
+ const handleToggleExpand = useCallback((pageId: number) => {
458
+ setExpandedKeys(prev => {
459
+ const newSet = new Set(prev);
460
+ if (newSet.has(pageId)) {
461
+ newSet.delete(pageId);
462
+ } else {
463
+ newSet.add(pageId);
464
+ }
465
+ return newSet;
466
+ });
467
+ }, []);
468
+
469
+ // Separate regular pages and virtual pages
470
+ const { regularPagesData, virtualPagesData } = useMemo(() => {
471
+ const pagesArray: any[] = Array.isArray(pageListData?.data) ? pageListData.data : [];
472
+ const regular: any[] = [];
473
+ const virtual: any[] = [];
474
+
475
+ pagesArray.forEach((page: any) => {
476
+ if (page.isVirtual) {
477
+ virtual.push(page);
478
+ } else {
479
+ regular.push(page);
480
+ }
481
+ });
482
+
483
+ return { regularPagesData: regular, virtualPagesData: virtual };
484
+ }, [pageListData]);
485
+
486
+ // Flatten tree data for table display (with depth info for indentation) - only for regular (non-virtual) pages
487
+ const flattenedTableData = useMemo(() => {
488
+ const nodeMapById = new Map<number, any>();
489
+
490
+ // First pass: create all nodes with pending changes applied (only regular pages)
491
+ regularPagesData.forEach((page: any) => {
492
+ const pageId = safelyConvertToNumber(page?.id);
493
+ if (pageId === null) return;
494
+
495
+ const pendingChange = pendingChanges.get(pageId);
496
+ const sortOrder = pendingChange?.sortOrder ?? page.sortOrder;
497
+ const parentId =
498
+ pendingChange?.parentId !== undefined ? pendingChange.parentId : (page.parentId ?? null);
499
+
500
+ nodeMapById.set(pageId, {
501
+ ...page,
502
+ id: pageId,
503
+ sortOrder,
504
+ parentId,
505
+ children: [],
506
+ });
507
+ });
508
+
509
+ // Second pass: build tree structure
510
+ const rootNodes: any[] = [];
511
+ nodeMapById.forEach(node => {
512
+ const parentId = node.parentId;
513
+ if (parentId !== null && nodeMapById.has(parentId)) {
514
+ nodeMapById.get(parentId).children.push(node);
515
+ } else {
516
+ rootNodes.push(node);
517
+ }
518
+ });
519
+
520
+ // Sort children by sortOrder
521
+ const sortNodes = (nodes: any[]) => {
522
+ nodes.sort((a, b) => (a.sortOrder ?? Infinity) - (b.sortOrder ?? Infinity));
523
+ nodes.forEach(node => {
524
+ if (node.children?.length > 0) {
525
+ sortNodes(node.children);
526
+ }
527
+ });
528
+ };
529
+ sortNodes(rootNodes);
530
+
531
+ // Flatten tree to array with depth info (respecting expanded state)
532
+ const result: any[] = [];
533
+ const flatten = (nodes: any[], depth: number, parentExpanded: boolean) => {
534
+ nodes.forEach(node => {
535
+ // Only show node if its parent is expanded (or it's a root node)
536
+ if (!parentExpanded && depth > 0) return;
537
+
538
+ const { children, ...nodeWithoutChildren } = node;
539
+ const hasChildren = children?.length > 0;
540
+ const isExpanded = expandedKeys.has(node.id);
541
+
542
+ result.push({
543
+ ...nodeWithoutChildren,
544
+ _depth: depth,
545
+ _hasChildren: hasChildren,
546
+ _isExpanded: isExpanded,
547
+ _isModified: pendingChanges.has(node.id),
548
+ });
549
+
550
+ if (hasChildren) {
551
+ flatten(children, depth + 1, isExpanded);
552
+ }
553
+ });
554
+ };
555
+ flatten(rootNodes, 0, true);
556
+
557
+ return result;
558
+ }, [regularPagesData, pendingChanges, expandedKeys]);
559
+
560
+ // Get sortable item IDs for dnd-kit
561
+ const sortableIds = useMemo(() => {
562
+ return flattenedTableData.map(item => String(item.id));
563
+ }, [flattenedTableData]);
564
+
565
+ // dnd-kit sensors
566
+ const sensors = useSensors(
567
+ useSensor(PointerSensor, {
568
+ activationConstraint: {
569
+ distance: 1,
570
+ },
571
+ }),
572
+ useSensor(KeyboardSensor, {
573
+ coordinateGetter: sortableKeyboardCoordinates,
574
+ }),
575
+ );
576
+
577
+ // Handle drag end for dnd-kit
578
+ const handleDragEnd = useCallback(
579
+ (event: DragEndEvent) => {
580
+ const { active, over } = event;
581
+
582
+ if (!over || active.id === over.id) return;
583
+
584
+ const draggedId = safelyConvertToNumber(active.id);
585
+ const overId = safelyConvertToNumber(over.id);
586
+
587
+ if (draggedId === null || overId === null) return;
588
+
589
+ const pagesArray: any[] = Array.isArray(pageListData?.data) ? pageListData.data : [];
590
+ const draggedPage = pagesArray.find(p => safelyConvertToNumber(p.id) === draggedId);
591
+ const overPage = pagesArray.find(p => safelyConvertToNumber(p.id) === overId);
592
+
593
+ if (!draggedPage || !overPage) return;
594
+
595
+ // Get target's parentId (with pending changes applied)
596
+ const overPendingChange = pendingChanges.get(overId);
597
+ const targetParentId =
598
+ overPendingChange?.parentId !== undefined
599
+ ? overPendingChange.parentId
600
+ : (overPage.parentId ?? null);
601
+
602
+ // Get dragged item's current parentId
603
+ const draggedPendingChange = pendingChanges.get(draggedId);
604
+ const currentParentId =
605
+ draggedPendingChange?.parentId !== undefined
606
+ ? draggedPendingChange.parentId
607
+ : (draggedPage.parentId ?? null);
608
+
609
+ // Determine new parentId - when dropping on an item, take its parentId (same level)
610
+ const newParentId = targetParentId;
611
+
612
+ // Get all siblings at the target parent level (with pending changes)
613
+ const getSiblingsWithPendingChanges = (parentId: number | null) => {
614
+ return pagesArray
615
+ .map(page => {
616
+ const pageId = safelyConvertToNumber(page.id);
617
+ if (pageId === null) return null;
618
+ const pending = pendingChanges.get(pageId);
619
+ return {
620
+ ...page,
621
+ id: pageId,
622
+ parentId:
623
+ pending?.parentId !== undefined ? pending.parentId : (page.parentId ?? null),
624
+ sortOrder: pending?.sortOrder ?? page.sortOrder ?? 0,
625
+ };
626
+ })
627
+ .filter(
628
+ (page): page is NonNullable<typeof page> => page !== null && page.parentId === parentId,
629
+ )
630
+ .sort((a, b) => a.sortOrder - b.sortOrder);
631
+ };
632
+
633
+ const siblings = getSiblingsWithPendingChanges(newParentId);
634
+
635
+ // Find the position of the over item in siblings
636
+ const overIndex = siblings.findIndex(s => s.id === overId);
637
+ const dragIndex = siblings.findIndex(s => s.id === draggedId);
638
+
639
+ // Calculate drop index
640
+ let dropIndex: number;
641
+ if (dragIndex === -1) {
642
+ // Dragged item is not in this parent group (moving to new parent)
643
+ dropIndex = overIndex;
644
+ } else if (dragIndex < overIndex) {
645
+ // Moving down
646
+ dropIndex = overIndex;
647
+ } else {
648
+ // Moving up
649
+ dropIndex = overIndex;
650
+ }
651
+
652
+ // Calculate new sort order
653
+ const newSortOrder = calculateNewSortOrder(siblings, dropIndex, draggedId);
654
+
655
+ // Check if anything changed
656
+ const parentChanged = newParentId !== currentParentId;
657
+ const orderChanged =
658
+ newSortOrder !== (draggedPendingChange?.sortOrder ?? draggedPage.sortOrder ?? 0);
659
+
660
+ if (!parentChanged && !orderChanged) return;
661
+
662
+ // Update pending changes
663
+ setPendingChanges(prev => {
664
+ const newChanges = new Map(prev);
665
+ const existingChange = newChanges.get(draggedId) || {};
666
+
667
+ newChanges.set(draggedId, {
668
+ ...existingChange,
669
+ ...(parentChanged && { parentId: newParentId }),
670
+ sortOrder: newSortOrder,
671
+ });
672
+
673
+ return newChanges;
674
+ });
675
+
676
+ message.info(`Page "${draggedPage.name}" moved. Click "Save Order" to apply changes.`);
677
+ },
678
+ [pageListData, pendingChanges, calculateNewSortOrder],
679
+ );
680
+
681
+ // Discard all pending changes
682
+ const handleDiscardChanges = useCallback(() => {
683
+ if (pendingChanges.size === 0) return;
684
+
685
+ Modal.confirm({
686
+ title: "Discard Changes",
687
+ content: `Are you sure you want to discard ${pendingChanges.size} pending change(s)?`,
688
+ okText: "Discard",
689
+ okType: "danger",
690
+ styles: { body: { padding: "16px 24px" } },
691
+ onOk: () => {
692
+ setPendingChanges(new Map());
693
+ message.success("Changes discarded");
694
+ },
695
+ });
696
+ }, [pendingChanges]);
697
+
698
+ // Save all pending changes
699
+ const handleSaveOrder = useCallback(async () => {
700
+ if (pendingChanges.size === 0) {
701
+ message.info("No changes to save");
702
+ return;
703
+ }
704
+
705
+ setIsSavingOrder(true);
706
+
707
+ try {
708
+ const updatePromises: Promise<any>[] = [];
709
+
710
+ pendingChanges.forEach((change, pageId) => {
711
+ const updateData: { sortOrder?: number; parentId?: number | null } = {};
712
+
713
+ if (change.sortOrder !== undefined) {
714
+ updateData.sortOrder = change.sortOrder;
715
+ }
716
+ if (change.parentId !== undefined) {
717
+ updateData.parentId = change.parentId;
718
+ }
719
+
720
+ if (Object.keys(updateData).length > 0) {
721
+ updatePromises.push(updatePageOrder(pageId, updateData));
722
+ }
723
+ });
724
+
725
+ await Promise.all(updatePromises);
726
+
727
+ message.success(`Successfully updated ${pendingChanges.size} page(s)`);
728
+ setPendingChanges(new Map());
729
+ refreshAllPageRelatedData();
730
+ } catch (error) {
731
+ console.error("Failed to save order:", error);
732
+ message.error("Failed to save some changes. Please try again.");
733
+ } finally {
734
+ setIsSavingOrder(false);
735
+ }
736
+ }, [pendingChanges, refreshAllPageRelatedData]);
737
+
738
+ // Build parent-children lookup for expand operations
739
+ const parentChildrenMap = useMemo(() => {
740
+ const childrenOf = new Map<number | null, number[]>(); // parentId -> childIds
741
+ for (const page of regularPagesData) {
742
+ const pageId = safelyConvertToNumber(page?.id);
743
+ if (pageId === null) continue;
744
+ const pendingChange = pendingChanges.get(pageId);
745
+ const parentId =
746
+ pendingChange?.parentId !== undefined ? pendingChange.parentId : (page.parentId ?? null);
747
+ if (!childrenOf.has(parentId)) childrenOf.set(parentId, []);
748
+ childrenOf.get(parentId)!.push(pageId);
749
+ }
750
+ return childrenOf;
751
+ }, [regularPagesData, pendingChanges]);
752
+
753
+ // Expand to N levels (single click = 2 levels)
754
+ const handleExpandLevels = useCallback(
755
+ (maxDepth: number) => {
756
+ const keysToExpand = new Set<number>();
757
+ const expand = (parentId: number | null, depth: number) => {
758
+ if (depth >= maxDepth) return;
759
+ const children = parentChildrenMap.get(parentId);
760
+ if (!children) return;
761
+ for (const childId of children) {
762
+ // Only expand nodes that have children
763
+ if (parentChildrenMap.has(childId)) {
764
+ keysToExpand.add(childId);
765
+ expand(childId, depth + 1);
766
+ }
767
+ }
768
+ };
769
+ expand(null, 0);
770
+ setExpandedKeys(keysToExpand);
771
+ },
772
+ [parentChildrenMap],
773
+ );
774
+
775
+ // Expand all nodes (double click)
776
+ const handleExpandAll = useCallback(() => {
777
+ // Expand every node that has children
778
+ const keysToExpand = new Set<number>();
779
+ for (const [parentId] of parentChildrenMap) {
780
+ if (parentId !== null) keysToExpand.add(parentId);
781
+ }
782
+ setExpandedKeys(keysToExpand);
783
+ }, [parentChildrenMap]);
784
+
785
+ // Collapse all nodes
786
+ const handleCollapseAll = useCallback(() => {
787
+ setExpandedKeys(new Set());
788
+ }, []);
789
+
790
+ // Common column renderers for both tables
791
+ const renderAssignedResourcesColumn = (record: any) => {
792
+ const permissionSummary = pageResourcePermissionSummaryMap[record.id];
793
+
794
+ if (!permissionSummary?.resourceNames.length) {
795
+ return <span style={{ color: "#999" }}>No resources assigned</span>;
796
+ }
797
+
798
+ const joinedResourceNames = permissionSummary.resourceNames.join(", ");
799
+ const shouldTruncateDisplay =
800
+ joinedResourceNames.length > RESOURCE_NAME_DISPLAY_TRUNCATION_LIMIT;
801
+ const truncatedDisplayValue = shouldTruncateDisplay
802
+ ? `${joinedResourceNames.slice(0, RESOURCE_NAME_DISPLAY_TRUNCATION_LIMIT)}...`
803
+ : joinedResourceNames;
804
+
805
+ return (
806
+ <Tooltip title={joinedResourceNames}>
807
+ <span>{truncatedDisplayValue}</span>
808
+ </Tooltip>
809
+ );
810
+ };
811
+
812
+ const renderPermissionActionsColumn = (record: any) => {
813
+ const permissionSummary = pageResourcePermissionSummaryMap[record.id];
814
+
815
+ if (!permissionSummary?.allPermissionActions.length) {
816
+ return <span style={{ color: "#999" }}>-</span>;
817
+ }
818
+
819
+ return (
820
+ <Space size={[4, 4]} wrap>
821
+ {permissionSummary.allPermissionActions.map(permissionAction => (
822
+ <Tag key={`${record.id}-${permissionAction}`} color="blue">
823
+ {permissionAction}
824
+ </Tag>
825
+ ))}
826
+ </Space>
827
+ );
828
+ };
829
+
830
+ // Handle click on add child page button
831
+ const handleAddChildPage = useCallback((parentPage: any) => {
832
+ const parentPath = parentPage.path || "";
833
+ const pathPrefix = parentPath.endsWith("/") ? parentPath : `${parentPath}/`;
834
+ setActiveModalState({
835
+ modalType: "create",
836
+ targetPageId: null,
837
+ defaultParentId: parentPage.id,
838
+ defaultPathPrefix: pathPrefix,
839
+ });
840
+ }, []);
841
+
842
+ const renderActionsColumn = (record: any, hasChildren: boolean = false) => (
843
+ <Space>
844
+ <Tooltip title="Add child page">
845
+ <Button
846
+ type="default"
847
+ size="small"
848
+ icon={<PlusOutlined />}
849
+ onClick={() => handleAddChildPage(record)}
850
+ />
851
+ </Tooltip>
852
+ <EditButton
853
+ hideText
854
+ size="small"
855
+ recordItemId={record.id}
856
+ onClick={() => setActiveModalState({ modalType: "edit", targetPageId: record.id })}
857
+ />
858
+ {/* Only show delete button for leaf nodes (pages without children) */}
859
+ {!hasChildren && (
860
+ <>
861
+ <Tooltip title="Assign roles">
862
+ <Button
863
+ type="default"
864
+ size="small"
865
+ icon={<TeamOutlined />}
866
+ onClick={() => setAssignRolesTarget({ pageId: record.id, pageName: record.name })}
867
+ />
868
+ </Tooltip>
869
+ <Tooltip title="View details">
870
+ <Button
871
+ type="default"
872
+ size="small"
873
+ icon={<EyeOutlined />}
874
+ onClick={() => setDrawerPage(record)}
875
+ />
876
+ </Tooltip>
877
+ <DeleteButton
878
+ hideText
879
+ size="small"
880
+ recordItemId={record.id}
881
+ resource="page"
882
+ onSuccess={() => refreshAllPageRelatedData()}
883
+ />
884
+ </>
885
+ )}
886
+ </Space>
887
+ );
888
+
889
+ // Render regular pages table with drag-drop
890
+ const renderRegularPagesTable = () => (
891
+ <>
892
+ <Space style={{ marginBottom: 16 }}>
893
+ <Tooltip title="Click: expand 2 levels / Double-click: expand all">
894
+ <Button
895
+ onClick={() => {
896
+ expandClickTimer.current = window.setTimeout(() => handleExpandLevels(1), 200);
897
+ }}
898
+ onDoubleClick={() => {
899
+ if (expandClickTimer.current) clearTimeout(expandClickTimer.current);
900
+ handleExpandAll();
901
+ }}
902
+ >
903
+ Expand All
904
+ </Button>
905
+ </Tooltip>
906
+ <Button onClick={handleCollapseAll}>Collapse All</Button>
907
+ {pendingChanges.size > 0 && (
908
+ <>
909
+ <Button
910
+ type="primary"
911
+ icon={<SaveOutlined />}
912
+ onClick={handleSaveOrder}
913
+ loading={isSavingOrder}
914
+ >
915
+ Save Order ({pendingChanges.size} change{pendingChanges.size > 1 ? "s" : ""})
916
+ </Button>
917
+ <Button icon={<UndoOutlined />} onClick={handleDiscardChanges} disabled={isSavingOrder}>
918
+ Discard Changes
919
+ </Button>
920
+ </>
921
+ )}
922
+ </Space>
923
+
924
+ {/* Info alert about drag and drop */}
925
+ <Alert
926
+ message="Drag and drop rows using the handle (⋮⋮) to reorder pages. Changes are saved in batch when you click 'Save Order'."
927
+ type="info"
928
+ showIcon
929
+ style={{ marginBottom: 16 }}
930
+ closable
931
+ />
932
+
933
+ {/* Table with dnd-kit drag and drop */}
934
+ <DndContext
935
+ sensors={sensors}
936
+ collisionDetection={closestCenter}
937
+ modifiers={[restrictToVerticalAxis]}
938
+ onDragEnd={handleDragEnd}
939
+ >
940
+ <SortableContext items={sortableIds} strategy={verticalListSortingStrategy}>
941
+ <Table
942
+ {...tableProps}
943
+ rowKey="id"
944
+ dataSource={flattenedTableData}
945
+ pagination={false}
946
+ components={{
947
+ body: {
948
+ row: DraggableRow,
949
+ },
950
+ }}
951
+ >
952
+ {/* Drag handle column */}
953
+ <Table.Column key="drag-handle" width={50} render={() => null} />
954
+ <Table.Column
955
+ dataIndex="name"
956
+ title="Page Name"
957
+ render={(text, record: any) => (
958
+ <span
959
+ style={{
960
+ paddingLeft: record._depth * 20,
961
+ display: "inline-flex",
962
+ alignItems: "center",
963
+ }}
964
+ >
965
+ {record._hasChildren ? (
966
+ <span
967
+ onClick={e => {
968
+ e.stopPropagation();
969
+ handleToggleExpand(record.id);
970
+ }}
971
+ style={{
972
+ cursor: "pointer",
973
+ marginRight: 8,
974
+ display: "inline-flex",
975
+ alignItems: "center",
976
+ }}
977
+ >
978
+ {record._isExpanded ? (
979
+ <DownOutlined style={{ fontSize: 10 }} />
980
+ ) : (
981
+ <RightOutlined style={{ fontSize: 10 }} />
982
+ )}
983
+ </span>
984
+ ) : (
985
+ <span style={{ width: 18 }} />
986
+ )}
987
+ {i18n.language === "zh_CN" ? record.zhName || text : record.enName || text}
988
+ {record._isModified && (
989
+ <Tag color="orange" style={{ marginLeft: 8, fontSize: 10 }}>
990
+ Modified
991
+ </Tag>
992
+ )}
993
+ </span>
994
+ )}
995
+ />
996
+ <Table.Column dataIndex="path" title="Page Path" />
997
+ <Table.Column
998
+ dataIndex="description"
999
+ title="Description"
1000
+ ellipsis={{ showTitle: true }}
1001
+ onCell={() => ({ style: { maxWidth: 200 } })}
1002
+ />
1003
+ <Table.Column dataIndex="creator" title="Creator" />
1004
+ <Table.Column
1005
+ title="Actions"
1006
+ render={(_, record: any) => renderActionsColumn(record, record._hasChildren)}
1007
+ />
1008
+ </Table>
1009
+ </SortableContext>
1010
+ </DndContext>
1011
+ </>
1012
+ );
1013
+
1014
+ // Render virtual pages table (simple table without drag-drop and tree structure)
1015
+ const renderVirtualPagesTable = () => (
1016
+ <Table rowKey="id" dataSource={virtualPagesData} pagination={{ pageSize: 20 }}>
1017
+ <Table.Column dataIndex="name" title="Page Name" />
1018
+ <Table.Column dataIndex="creator" title="Creator" />
1019
+ <Table.Column
1020
+ dataIndex="description"
1021
+ title="Description"
1022
+ ellipsis={{ showTitle: true }}
1023
+ onCell={() => ({ style: { maxWidth: 200 } })}
1024
+ />
1025
+ <Table.Column
1026
+ title="Actions"
1027
+ render={(_, record: any) => renderActionsColumn(record, false)}
1028
+ />
1029
+ </Table>
1030
+ );
1031
+
1032
+ return (
1033
+ <div>
1034
+ <Space style={{ marginBottom: 16 }}>
1035
+ <Button
1036
+ type="primary"
1037
+ onClick={() =>
1038
+ setActiveModalState({
1039
+ modalType: "create",
1040
+ targetPageId: null,
1041
+ })
1042
+ }
1043
+ >
1044
+ Create Page
1045
+ </Button>
1046
+ </Space>
1047
+
1048
+ <Tabs
1049
+ activeKey={activeTab}
1050
+ onChange={setActiveTab}
1051
+ items={[
1052
+ {
1053
+ key: "regular",
1054
+ label: `Regular Pages (${regularPagesData.length})`,
1055
+ children: renderRegularPagesTable(),
1056
+ },
1057
+ {
1058
+ key: "virtual",
1059
+ label: `Virtual Pages (${virtualPagesData.length})`,
1060
+ children: renderVirtualPagesTable(),
1061
+ },
1062
+ ]}
1063
+ />
1064
+
1065
+ {activeModalState.modalType === "edit" && activeModalState.targetPageId && (
1066
+ <EditPageModal
1067
+ pageIdToEdit={activeModalState.targetPageId}
1068
+ onModalClose={handleModalClose}
1069
+ onPageEditSuccess={handleModalOperationSuccess}
1070
+ parentPageTreeOptions={pageMenuTreeOptionsData}
1071
+ allAvailableResources={availableResourcesListForModal}
1072
+ currentPageResourceAssignments={getPageResourceAssignmentsForModal(
1073
+ activeModalState.targetPageId,
1074
+ )}
1075
+ allPagesData={pageListData?.data || []}
1076
+ />
1077
+ )}
1078
+
1079
+ {drawerPage && (
1080
+ <PageDetailDrawer
1081
+ page={drawerPage}
1082
+ open={!!drawerPage}
1083
+ onClose={() => setDrawerPage(null)}
1084
+ />
1085
+ )}
1086
+
1087
+ {assignRolesTarget && (
1088
+ <AssignRolesModal
1089
+ pageId={assignRolesTarget.pageId}
1090
+ pageName={assignRolesTarget.pageName}
1091
+ open={!!assignRolesTarget}
1092
+ onClose={() => setAssignRolesTarget(null)}
1093
+ onSuccess={() => {
1094
+ setAssignRolesTarget(null);
1095
+ refreshAllPageRelatedData();
1096
+ }}
1097
+ />
1098
+ )}
1099
+
1100
+ {activeModalState.modalType === "create" && (
1101
+ <CreatePageModal
1102
+ onModalClose={handleModalClose}
1103
+ onPageCreationSuccess={handleModalOperationSuccess}
1104
+ parentPageTreeOptions={pageMenuTreeOptionsData}
1105
+ allAvailableResources={availableResourcesListForModal}
1106
+ allPagesData={pageListData?.data || []}
1107
+ defaultParentId={activeModalState.defaultParentId}
1108
+ defaultPathPrefix={activeModalState.defaultPathPrefix}
1109
+ />
1110
+ )}
1111
+ </div>
1112
+ );
1113
+ };