@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.
- package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +40 -26
- package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +30 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/example.prisma +33 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +163 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +70 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Game.ts +6 -6
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/ITActivityDay.ts +70 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Log.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Role.ts +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env +20 -9
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env.local +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.eslintrc.js +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/.prettierignore +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/README.md +2 -18
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/gadmin-cli.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/migrate-between-pg-schemas.js +1232 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +65 -38
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma/.generator.prisma +4 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/games.ts +1 -71
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +17 -21
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +278 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/seedDataMngtPages.ts +258 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/users.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/alias.config.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.controller.ts +151 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.module.ts +29 -13
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.service.ts +151 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/auth.guard.ts +87 -41
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/http-cache.interceptor.ts +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/logger.ts +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/safe-log.util.ts +176 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/{yufuid.ts → taihu.ts} +49 -34
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/tracing.ts +174 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/trim.pipe.ts +51 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/utils.ts +91 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/woaAuth.ts +25 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +22 -12
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.ts +190 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +338 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.ts +83 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.ts +188 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.ts +83 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +250 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.ts +1051 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.module.ts +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.ts +219 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.ts +199 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +210 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +12 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.ts +849 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/roles-refresher.service.ts +133 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.ts +201 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.ts +196 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.ts +216 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +20 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +198 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +104 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +130 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/tsconfig.json +18 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/index.html +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +34 -42
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/postcss.config.cjs +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/App.tsx +111 -185
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/auditLogProvider.ts +5 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/color-mode/index.tsx +49 -51
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +38 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/{header/index.tsx → header.tsx} +22 -31
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/index.ts +3 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx +32 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +331 -166
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/pagination-total.tsx +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/role-tag.tsx +44 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/text.tsx +74 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +258 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +16 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/audit-log.enum.ts +13 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/index.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +211 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useFetchData.ts +33 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useRoles.ts +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useUserPageAccess.ts +339 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +8 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/index.tsx +3 -11
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/en/common.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/zh_CN/common.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.css +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +134 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/list.tsx +213 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +168 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/CreatePageModal.tsx +42 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/EditPageModal.tsx +42 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +101 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +731 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +36 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +1113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/queries.ts +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +44 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/list.tsx +243 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permission-readme/index.tsx +1088 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/CreateModal.tsx +25 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/EditModal.tsx +28 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +160 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/modal.tsx +202 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +9 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +184 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/queries.ts +10 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/types.ts +9 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/CreateModal.tsx +30 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/EditModal.tsx +47 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +56 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +302 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +35 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +382 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/queries.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/types.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/list.tsx +243 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/list.tsx +243 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/create-modal.tsx +17 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/edit-modal.tsx +19 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +188 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/index.ts +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/role-tag.tsx +48 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +140 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/create.tsx +113 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/edit.tsx +122 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.tsx +6 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +342 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/queries.ts +14 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/show.tsx +61 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +132 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/fc.css +58 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +128 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-drawer.module.css +18 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-page.module.css +21 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/audit-log.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/index.ts +3 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/role.ts +7 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/user.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-name-initials.ts +8 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-random-color.ts +27 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/index.ts +2 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/utils.tsx +5 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/vite-env.d.ts +1 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +3 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/vite.config.ts +31 -0
- package/package.json +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/sample.prisma +0 -65
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Source.ts +0 -76
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Tasklog.ts +0 -76
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/roles.ts +0 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/craco.config.js +0 -27
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/public/index.html +0 -53
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/styles.ts +0 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/react-app-env.d.ts +0 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/reportWebVitals.ts +0 -15
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.less +0 -79
- /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
|
+
};
|