robobyte-front-builder 1.0.0

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 (614) hide show
  1. package/jsconfig.json +28 -0
  2. package/next.config.js +75 -0
  3. package/package.json +163 -0
  4. package/public/fonts/NotoSansArabic-VariableFont_wdth,wght.ttf +0 -0
  5. package/public/fonts/font.js +2 -0
  6. package/public/fonts/logo.js +2 -0
  7. package/public/images/appicon.png +0 -0
  8. package/public/images/appicon1.png +0 -0
  9. package/public/images/asiacell.png +0 -0
  10. package/public/images/avatars/1.png +0 -0
  11. package/public/images/avatars/3.png +0 -0
  12. package/public/images/avatars/4.png +0 -0
  13. package/public/images/avatars/5.png +0 -0
  14. package/public/images/cards/trophy.png +0 -0
  15. package/public/images/favicon.png +0 -0
  16. package/public/images/favicon1.png +0 -0
  17. package/public/images/icons/bag.png +0 -0
  18. package/public/images/icons/briefcase.png +0 -0
  19. package/public/images/icons/calendar.png +0 -0
  20. package/public/images/icons/dashboard.png +0 -0
  21. package/public/images/icons/dollar.png +0 -0
  22. package/public/images/icons/person.png +0 -0
  23. package/public/images/icons/profit.png +0 -0
  24. package/public/images/icons/setting.png +0 -0
  25. package/public/images/misc/chart.png +0 -0
  26. package/public/images/misc/paypal.png +0 -0
  27. package/public/images/pages/401.png +0 -0
  28. package/public/images/pages/404.png +0 -0
  29. package/public/images/pages/500.png +0 -0
  30. package/public/images/pages/auth-v2-forgot-password-illustration-bordered-dark.png +0 -0
  31. package/public/images/pages/auth-v2-forgot-password-illustration-bordered-light.png +0 -0
  32. package/public/images/pages/auth-v2-forgot-password-illustration-dark.png +0 -0
  33. package/public/images/pages/auth-v2-forgot-password-illustration-light.png +0 -0
  34. package/public/images/pages/auth-v2-forgot-password-mask-dark.png +0 -0
  35. package/public/images/pages/auth-v2-forgot-password-mask-light.png +0 -0
  36. package/public/images/pages/auth-v2-login-illustration-bordered-dark.png +0 -0
  37. package/public/images/pages/auth-v2-login-illustration-bordered-light.png +0 -0
  38. package/public/images/pages/auth-v2-login-illustration-dark.png +0 -0
  39. package/public/images/pages/auth-v2-login-illustration-light.png +0 -0
  40. package/public/images/pages/auth-v2-login-mask-dark.png +0 -0
  41. package/public/images/pages/auth-v2-login-mask-light.png +0 -0
  42. package/public/images/pages/auth-v2-register-illustration-bordered-dark.png +0 -0
  43. package/public/images/pages/auth-v2-register-illustration-bordered-light.png +0 -0
  44. package/public/images/pages/auth-v2-register-illustration-dark.png +0 -0
  45. package/public/images/pages/auth-v2-register-illustration-light.png +0 -0
  46. package/public/images/pages/auth-v2-register-mask-dark.png +0 -0
  47. package/public/images/pages/auth-v2-register-mask-light.png +0 -0
  48. package/public/images/pages/misc-401-object.png +0 -0
  49. package/public/images/pages/misc-404-object.png +0 -0
  50. package/public/images/pages/misc-500-object.png +0 -0
  51. package/public/images/pages/misc-coming-soon-object.png +0 -0
  52. package/public/images/pages/misc-mask-dark.png +0 -0
  53. package/public/images/pages/misc-mask-light.png +0 -0
  54. package/public/images/white.jpg +0 -0
  55. package/public/images/zain.png +0 -0
  56. package/public/locales/ar.json +35 -0
  57. package/public/navigationTest/Navigation.json +13 -0
  58. package/public/vercel.svg +4 -0
  59. package/src/@core/components/auth/AclGuard.js +55 -0
  60. package/src/@core/components/auth/AuthGuard.js +40 -0
  61. package/src/@core/components/auth/GuestGuard.js +30 -0
  62. package/src/@core/components/custom-inputs/Horizontal.jsx +143 -0
  63. package/src/@core/components/custom-inputs/Image.jsx +78 -0
  64. package/src/@core/components/custom-inputs/Vertical.jsx +113 -0
  65. package/src/@core/components/customizer/index.jsx +470 -0
  66. package/src/@core/components/customizer/styles.module.css +169 -0
  67. package/src/@core/components/mui/Avatar.jsx +41 -0
  68. package/src/@core/components/mui/Badge.jsx +20 -0
  69. package/src/@core/components/mui/IconButton.jsx +74 -0
  70. package/src/@core/components/mui/TabList.jsx +60 -0
  71. package/src/@core/components/option-menu/index.jsx +137 -0
  72. package/src/@core/components/scroll-to-top/index.jsx +43 -0
  73. package/src/@core/components/spinner/index.js +26 -0
  74. package/src/@core/components/window-wrapper/index.js +27 -0
  75. package/src/@core/contexts/settingsContext.jsx +98 -0
  76. package/src/@core/hooks/useBgColor.js +63 -0
  77. package/src/@core/hooks/useImageVariant.js +27 -0
  78. package/src/@core/hooks/useLayoutInit.js +37 -0
  79. package/src/@core/hooks/useObjectCookie.js +18 -0
  80. package/src/@core/hooks/useSettings.jsx +15 -0
  81. package/src/@core/layouts/BlankLayout.js +37 -0
  82. package/src/@core/layouts/BlankLayoutWithAppBar.js +51 -0
  83. package/src/@core/layouts/HorizontalLayout.jsx +151 -0
  84. package/src/@core/layouts/Layout.js +39 -0
  85. package/src/@core/layouts/VerticalLayout.jsx +124 -0
  86. package/src/@core/layouts/components/blank-layout-with-appBar/index.js +115 -0
  87. package/src/@core/layouts/components/horizontal/app-bar-content/index.js +67 -0
  88. package/src/@core/layouts/components/horizontal/navigation/HorizontalNavGroup.js +352 -0
  89. package/src/@core/layouts/components/horizontal/navigation/HorizontalNavItems.js +21 -0
  90. package/src/@core/layouts/components/horizontal/navigation/HorizontalNavLink.js +195 -0
  91. package/src/@core/layouts/components/horizontal/navigation/index.js +31 -0
  92. package/src/@core/layouts/components/shared-components/LanguageDropdown.js +96 -0
  93. package/src/@core/layouts/components/shared-components/ModeToggler.js +32 -0
  94. package/src/@core/layouts/components/shared-components/NotificationDropdown.js +226 -0
  95. package/src/@core/layouts/components/shared-components/UserDropdown.js +177 -0
  96. package/src/@core/layouts/components/shared-components/footer/FooterContent.js +46 -0
  97. package/src/@core/layouts/components/shared-components/footer/index.js +61 -0
  98. package/src/@core/layouts/components/vertical/appBar/index.js +74 -0
  99. package/src/@core/layouts/components/vertical/navigation/Drawer.js +122 -0
  100. package/src/@core/layouts/components/vertical/navigation/VerticalNavGroup.js +435 -0
  101. package/src/@core/layouts/components/vertical/navigation/VerticalNavHeader.js +180 -0
  102. package/src/@core/layouts/components/vertical/navigation/VerticalNavItems.js +26 -0
  103. package/src/@core/layouts/components/vertical/navigation/VerticalNavLink.js +258 -0
  104. package/src/@core/layouts/components/vertical/navigation/VerticalNavSectionTitle.js +102 -0
  105. package/src/@core/layouts/components/vertical/navigation/index.js +169 -0
  106. package/src/@core/layouts/utils.js +69 -0
  107. package/src/@core/styles/Table.module.css +93 -0
  108. package/src/@core/styles/horizontal/menuItemStyles.js +100 -0
  109. package/src/@core/styles/horizontal/menuRootStyles.js +19 -0
  110. package/src/@core/styles/libs/fullcalendar/index.js +461 -0
  111. package/src/@core/styles/libs/keen-slider/index.js +111 -0
  112. package/src/@core/styles/libs/react-apexcharts/index.js +107 -0
  113. package/src/@core/styles/libs/react-cleave/index.js +33 -0
  114. package/src/@core/styles/libs/react-credit-cards/index.js +11 -0
  115. package/src/@core/styles/libs/react-datepicker/index.js +388 -0
  116. package/src/@core/styles/libs/react-draft-wysiwyg/index.js +144 -0
  117. package/src/@core/styles/libs/react-dropzone/index.js +76 -0
  118. package/src/@core/styles/libs/react-hot-toast/index.js +37 -0
  119. package/src/@core/styles/libs/recharts/index.js +47 -0
  120. package/src/@core/styles/stepper.js +103 -0
  121. package/src/@core/styles/vertical/menuItemStyles.js +138 -0
  122. package/src/@core/styles/vertical/menuSectionStyles.js +54 -0
  123. package/src/@core/styles/vertical/navigationCustomStyles.js +62 -0
  124. package/src/@core/svg/ContentCompact.jsx +17 -0
  125. package/src/@core/svg/ContentWide.jsx +17 -0
  126. package/src/@core/svg/DirectionLtr.jsx +93 -0
  127. package/src/@core/svg/DirectionRtl.jsx +93 -0
  128. package/src/@core/svg/LayoutCollapsed.jsx +59 -0
  129. package/src/@core/svg/LayoutHorizontal.jsx +42 -0
  130. package/src/@core/svg/LayoutVertical.jsx +59 -0
  131. package/src/@core/svg/Logo.jsx +76 -0
  132. package/src/@core/svg/SkinBordered.jsx +54 -0
  133. package/src/@core/svg/SkinDefault.jsx +59 -0
  134. package/src/@core/tailwind/plugin.js +78 -0
  135. package/src/@core/theme/ThemeComponent.js +63 -0
  136. package/src/@core/theme/ThemeOptions.js +71 -0
  137. package/src/@core/theme/breakpoints/index.js +11 -0
  138. package/src/@core/theme/colorSchemes.js +326 -0
  139. package/src/@core/theme/customShadows.js +11 -0
  140. package/src/@core/theme/globalStyles.js +81 -0
  141. package/src/@core/theme/index.js +42 -0
  142. package/src/@core/theme/overrides/accordion.js +51 -0
  143. package/src/@core/theme/overrides/accordion.jsx +85 -0
  144. package/src/@core/theme/overrides/alerts.js +110 -0
  145. package/src/@core/theme/overrides/alerts.jsx +180 -0
  146. package/src/@core/theme/overrides/autocomplete.js +14 -0
  147. package/src/@core/theme/overrides/autocomplete.jsx +68 -0
  148. package/src/@core/theme/overrides/avatar.js +38 -0
  149. package/src/@core/theme/overrides/avatars.js +27 -0
  150. package/src/@core/theme/overrides/backdrop.js +22 -0
  151. package/src/@core/theme/overrides/badges.js +16 -0
  152. package/src/@core/theme/overrides/breadcrumbs.js +11 -0
  153. package/src/@core/theme/overrides/button-group.js +84 -0
  154. package/src/@core/theme/overrides/button.js +93 -0
  155. package/src/@core/theme/overrides/buttonGroup.js +9 -0
  156. package/src/@core/theme/overrides/card.js +83 -0
  157. package/src/@core/theme/overrides/checkbox.jsx +95 -0
  158. package/src/@core/theme/overrides/chip.js +72 -0
  159. package/src/@core/theme/overrides/dataGrid.js +114 -0
  160. package/src/@core/theme/overrides/dateTimePicker.js +65 -0
  161. package/src/@core/theme/overrides/dialog.js +120 -0
  162. package/src/@core/theme/overrides/divider.js +13 -0
  163. package/src/@core/theme/overrides/drawer.js +20 -0
  164. package/src/@core/theme/overrides/fab.js +13 -0
  165. package/src/@core/theme/overrides/form-control-label.js +19 -0
  166. package/src/@core/theme/overrides/icon-button.js +145 -0
  167. package/src/@core/theme/overrides/index.js +103 -0
  168. package/src/@core/theme/overrides/input.js +72 -0
  169. package/src/@core/theme/overrides/link.js +9 -0
  170. package/src/@core/theme/overrides/list.js +44 -0
  171. package/src/@core/theme/overrides/menu.js +25 -0
  172. package/src/@core/theme/overrides/pagination.js +41 -0
  173. package/src/@core/theme/overrides/paper.js +9 -0
  174. package/src/@core/theme/overrides/popover.js +16 -0
  175. package/src/@core/theme/overrides/progress.js +38 -0
  176. package/src/@core/theme/overrides/radio.jsx +80 -0
  177. package/src/@core/theme/overrides/rating.js +16 -0
  178. package/src/@core/theme/overrides/rating.jsx +32 -0
  179. package/src/@core/theme/overrides/select.js +19 -0
  180. package/src/@core/theme/overrides/select.jsx +52 -0
  181. package/src/@core/theme/overrides/slider.js +97 -0
  182. package/src/@core/theme/overrides/snackbar.js +19 -0
  183. package/src/@core/theme/overrides/switch.js +73 -0
  184. package/src/@core/theme/overrides/switches.js +25 -0
  185. package/src/@core/theme/overrides/table-pagination.js +39 -0
  186. package/src/@core/theme/overrides/table.js +81 -0
  187. package/src/@core/theme/overrides/tabs.js +30 -0
  188. package/src/@core/theme/overrides/timeline.js +80 -0
  189. package/src/@core/theme/overrides/toggle-button.js +33 -0
  190. package/src/@core/theme/overrides/toggleButton.js +16 -0
  191. package/src/@core/theme/overrides/tooltip.js +21 -0
  192. package/src/@core/theme/overrides/typography.js +13 -0
  193. package/src/@core/theme/palette/index.js +107 -0
  194. package/src/@core/theme/shadows/index.js +61 -0
  195. package/src/@core/theme/shadows.js +12 -0
  196. package/src/@core/theme/spacing/index.js +3 -0
  197. package/src/@core/theme/spacing.js +5 -0
  198. package/src/@core/theme/typography/index.js +65 -0
  199. package/src/@core/theme/typography.js +84 -0
  200. package/src/@core/utils/create-emotion-cache.js +5 -0
  201. package/src/@core/utils/hex-to-rgba.js +11 -0
  202. package/src/@core/utils/serverHelpers.js +45 -0
  203. package/src/@menu/components/RouterLink.jsx +18 -0
  204. package/src/@menu/components/horizontal-menu/HorizontalNav.jsx +88 -0
  205. package/src/@menu/components/horizontal-menu/Menu.jsx +83 -0
  206. package/src/@menu/components/horizontal-menu/MenuButton.jsx +100 -0
  207. package/src/@menu/components/horizontal-menu/MenuItem.jsx +183 -0
  208. package/src/@menu/components/horizontal-menu/SubMenu.jsx +418 -0
  209. package/src/@menu/components/horizontal-menu/SubMenuContent.jsx +41 -0
  210. package/src/@menu/components/horizontal-menu/VerticalNavInHorizontal.jsx +20 -0
  211. package/src/@menu/components/vertical-menu/Menu.jsx +161 -0
  212. package/src/@menu/components/vertical-menu/MenuButton.jsx +95 -0
  213. package/src/@menu/components/vertical-menu/MenuItem.jsx +180 -0
  214. package/src/@menu/components/vertical-menu/MenuSection.jsx +124 -0
  215. package/src/@menu/components/vertical-menu/NavCollapseIcons.jsx +70 -0
  216. package/src/@menu/components/vertical-menu/NavHeader.jsx +39 -0
  217. package/src/@menu/components/vertical-menu/SubMenu.jsx +420 -0
  218. package/src/@menu/components/vertical-menu/SubMenuContent.jsx +101 -0
  219. package/src/@menu/components/vertical-menu/VerticalNav.jsx +216 -0
  220. package/src/@menu/contexts/horizontalNavContext.jsx +29 -0
  221. package/src/@menu/contexts/verticalNavContext.jsx +65 -0
  222. package/src/@menu/defaultConfigs.js +12 -0
  223. package/src/@menu/hooks/useHorizontalMenu.jsx +19 -0
  224. package/src/@menu/hooks/useHorizontalNav.jsx +19 -0
  225. package/src/@menu/hooks/useMediaQuery.jsx +29 -0
  226. package/src/@menu/hooks/useVerticalMenu.jsx +19 -0
  227. package/src/@menu/hooks/useVerticalNav.jsx +19 -0
  228. package/src/@menu/horizontal-menu/index.jsx +8 -0
  229. package/src/@menu/styles/StyledBackdrop.jsx +15 -0
  230. package/src/@menu/styles/StyledMenuIcon.jsx +12 -0
  231. package/src/@menu/styles/StyledMenuLabel.jsx +16 -0
  232. package/src/@menu/styles/StyledMenuPrefix.jsx +10 -0
  233. package/src/@menu/styles/StyledMenuSectionLabel.jsx +21 -0
  234. package/src/@menu/styles/StyledMenuSuffix.jsx +10 -0
  235. package/src/@menu/styles/StyledSubMenuContent.jsx +43 -0
  236. package/src/@menu/styles/horizontal/StyledHorizontalMenu.jsx +13 -0
  237. package/src/@menu/styles/horizontal/StyledHorizontalMenuItem.jsx +26 -0
  238. package/src/@menu/styles/horizontal/StyledHorizontalNav.jsx +11 -0
  239. package/src/@menu/styles/horizontal/StyledHorizontalNavExpandIcon.jsx +33 -0
  240. package/src/@menu/styles/horizontal/StyledHorizontalSubMenuContent.jsx +18 -0
  241. package/src/@menu/styles/horizontal/StyledHorizontalSubMenuContentWrapper.jsx +10 -0
  242. package/src/@menu/styles/horizontal/horizontalUl.module.css +15 -0
  243. package/src/@menu/styles/styles.module.css +5 -0
  244. package/src/@menu/styles/vertical/StyledVerticalMenu.jsx +16 -0
  245. package/src/@menu/styles/vertical/StyledVerticalMenuItem.jsx +28 -0
  246. package/src/@menu/styles/vertical/StyledVerticalMenuSection.jsx +23 -0
  247. package/src/@menu/styles/vertical/StyledVerticalNav.jsx +67 -0
  248. package/src/@menu/styles/vertical/StyledVerticalNavBgColorContainer.jsx +15 -0
  249. package/src/@menu/styles/vertical/StyledVerticalNavContainer.jsx +23 -0
  250. package/src/@menu/styles/vertical/StyledVerticalNavExpandIcon.jsx +25 -0
  251. package/src/@menu/styles/vertical/verticalNavBgImage.module.css +10 -0
  252. package/src/@menu/svg/ChevronRight.jsx +9 -0
  253. package/src/@menu/svg/Close.jsx +12 -0
  254. package/src/@menu/svg/RadioCircle.jsx +12 -0
  255. package/src/@menu/svg/RadioCircleMarked.jsx +13 -0
  256. package/src/@menu/utils/menuClasses.js +44 -0
  257. package/src/@menu/utils/menuUtils.jsx +145 -0
  258. package/src/@menu/vertical-menu/index.jsx +11 -0
  259. package/src/configs/Permissions/PermissionsActions.json +6 -0
  260. package/src/configs/Permissions/PermissionsSubjects.json +107 -0
  261. package/src/configs/acl.js +115 -0
  262. package/src/configs/auth.js +5 -0
  263. package/src/configs/aws-exports.js +30 -0
  264. package/src/configs/firebase.js +25 -0
  265. package/src/configs/i18n.js +34 -0
  266. package/src/configs/izColors.json +11 -0
  267. package/src/configs/primaryColorConfig.js +35 -0
  268. package/src/configs/themeConfig.js +44 -0
  269. package/src/context/AuthContext.js +179 -0
  270. package/src/context/BuilderContext.jsx +209 -0
  271. package/src/context/SystemContext.js +99 -0
  272. package/src/hooks/useAuth.js +4 -0
  273. package/src/layouts/UserLayout.js +94 -0
  274. package/src/layouts/UserThemeOptions.js +191 -0
  275. package/src/layouts/components/Direction.js +30 -0
  276. package/src/layouts/components/HtmlTooltip.js +15 -0
  277. package/src/layouts/components/Translations.js +11 -0
  278. package/src/layouts/components/UserDropdown.js +217 -0
  279. package/src/layouts/components/UserIcon.js +40 -0
  280. package/src/layouts/components/acl/Can.js +6 -0
  281. package/src/layouts/components/acl/CanViewNavGroup.js +36 -0
  282. package/src/layouts/components/acl/CanViewNavLink.js +17 -0
  283. package/src/layouts/components/acl/CanViewNavSectionTitle.js +17 -0
  284. package/src/layouts/components/horizontal/AppBarContent.js +39 -0
  285. package/src/layouts/components/horizontal/ServerSideNavItems.js +44 -0
  286. package/src/layouts/components/mui/StepperComps.js +55 -0
  287. package/src/layouts/components/vertical/AppBarContent.js +35 -0
  288. package/src/layouts/components/vertical/ServerSideNavItems.js +44 -0
  289. package/src/lib/index.js +75 -0
  290. package/src/lib/navigation/NavigationExtensionContext.jsx +81 -0
  291. package/src/lib/navigation/mergeNavExtensions.js +66 -0
  292. package/src/lib/navigation/useNavExtension.js +54 -0
  293. package/src/lib/providers/RoboByteFrontBuilderProvider.jsx +57 -0
  294. package/src/libs/ApexCharts.jsx +5 -0
  295. package/src/libs/ReactPlayer.jsx +5 -0
  296. package/src/libs/Recharts.jsx +4 -0
  297. package/src/libs/auth.js +124 -0
  298. package/src/libs/styles/AppFullCalendar.js +505 -0
  299. package/src/libs/styles/AppKeenSlider.js +116 -0
  300. package/src/libs/styles/AppReactApexCharts.jsx +110 -0
  301. package/src/libs/styles/AppReactDatepicker.jsx +470 -0
  302. package/src/libs/styles/AppReactDropzone.js +76 -0
  303. package/src/libs/styles/AppReactToastify.jsx +108 -0
  304. package/src/libs/styles/AppRecharts.js +55 -0
  305. package/src/libs/styles/inputOtp.module.css +39 -0
  306. package/src/libs/styles/tiptapEditor.css +72 -0
  307. package/src/navigation/horizontal/index.js +246 -0
  308. package/src/navigation/vertical/index.js +253 -0
  309. package/src/pages/401.js +70 -0
  310. package/src/pages/404.js +67 -0
  311. package/src/pages/500.js +68 -0
  312. package/src/pages/[slug].js +115 -0
  313. package/src/pages/_app.js +148 -0
  314. package/src/pages/_document.js +72 -0
  315. package/src/pages/api/navigation/regenerate-registry.js +116 -0
  316. package/src/pages/api/navigation/save.js +218 -0
  317. package/src/pages/authModule/acl/index.js +48 -0
  318. package/src/pages/authModule/forgot-password/index.js +228 -0
  319. package/src/pages/authModule/permissions/rolePermissions/[id]/rolePermissionsUser/index.js +392 -0
  320. package/src/pages/authModule/permissions/rolePermissions/index.js +343 -0
  321. package/src/pages/authModule/permissions/systemPermissions/index.js +354 -0
  322. package/src/pages/authModule/privacy/index.js +721 -0
  323. package/src/pages/authModule/users/index.js +210 -0
  324. package/src/pages/index.js +44 -0
  325. package/src/pages/login/index.js +328 -0
  326. package/src/pages/mainHome/index.js +181 -0
  327. package/src/pages/navigatorBuilder/index.jsx +2070 -0
  328. package/src/pages/reportModule/reportBuilder/index.js +1361 -0
  329. package/src/pages/reportModule/reportBuilder/reportViewer/index.js +187 -0
  330. package/src/pages/reportModule/reportBuilder/reports/index.js +198 -0
  331. package/src/pages/viewBuilder/index.jsx +94 -0
  332. package/src/pages/viewBuilder/viewPage/index.jsx +46 -0
  333. package/src/pages/viewBuilder/views/index.js +146 -0
  334. package/src/pages/viewer/[id]/index.js +88 -0
  335. package/src/services/ContentTypes.js +10 -0
  336. package/src/services/DeleteService.js +77 -0
  337. package/src/services/Endpoints/BunnyEndpoints.js +26 -0
  338. package/src/services/Endpoints/CaseEndpoints.js +29 -0
  339. package/src/services/Endpoints/CaseTypeEndpoints.js +29 -0
  340. package/src/services/Endpoints/ContactsEndpoints.js +81 -0
  341. package/src/services/Endpoints/CourtEndpoints.js +29 -0
  342. package/src/services/Endpoints/FilterEndpoints.js +31 -0
  343. package/src/services/Endpoints/NavigatorEndpoints.js +21 -0
  344. package/src/services/Endpoints/ReportBuilderEndpoints.js +45 -0
  345. package/src/services/Endpoints/UiBuilderEndpoints.js +37 -0
  346. package/src/services/Endpoints/UserTableTemplateEndpoints.js +26 -0
  347. package/src/services/Endpoints/UsersEndpoints.js +142 -0
  348. package/src/services/Endpoints/ViewEndpoints.js +28 -0
  349. package/src/services/Endpoints/ViewPermissionEndpoints.js +37 -0
  350. package/src/services/Endpoints/ViewPermissionRoleEndpoints.js +39 -0
  351. package/src/services/Endpoints/ViewPermissionRoleItemsEndpoints.js +32 -0
  352. package/src/services/Endpoints/ViewPermissionRoleUsersEndpoints.js +28 -0
  353. package/src/services/Endpoints/WidgetEndpoints.js +28 -0
  354. package/src/services/Endpoints.js +49 -0
  355. package/src/services/External/BunnyCdn/BunnyUploadFile.js +168 -0
  356. package/src/services/GetService.js +67 -0
  357. package/src/services/MetaDataTemplate/index.js +5 -0
  358. package/src/services/PatchService.js +83 -0
  359. package/src/services/PostService.js +81 -0
  360. package/src/services/StreamService.js +82 -0
  361. package/src/services/UpdateService.js +82 -0
  362. package/src/services/auth/AuthService.js +47 -0
  363. package/src/services/auth/GetUsersService.js +38 -0
  364. package/src/services/auth/PostRegisterUser.js +29 -0
  365. package/src/services/auth/PostResetPasswordService.js +28 -0
  366. package/src/services/auth/PutAccountService.js +29 -0
  367. package/src/services/auth/PutRefreshToken.js +69 -0
  368. package/src/services/auth/RefreshToken.js +0 -0
  369. package/src/services/builderHelper/actionExecutor.js +73 -0
  370. package/src/services/builderHelper/builderHelper.js +99 -0
  371. package/src/services/builderHelper/index.js +2 -0
  372. package/src/services/builderHelper/jsExecutor.js +115 -0
  373. package/src/services/builderHelper/layoutHelpers.js +231 -0
  374. package/src/services/builderHelper/nodeFactory.js +44 -0
  375. package/src/services/builderHelper/resolveProps.js +34 -0
  376. package/src/services/builderHelper/tree.js +131 -0
  377. package/src/services/components/UniversalNestedAutocomplete.js +331 -0
  378. package/src/services/components/agGridAutoComplete.js +172 -0
  379. package/src/services/components/universalAutoComplete.js +207 -0
  380. package/src/services/enums/ReportBuilderTypeEnum.js +18 -0
  381. package/src/services/helper/FilterFormat.js +70 -0
  382. package/src/services/helper/FormatCurrencyIQD.js +11 -0
  383. package/src/services/helper/datagridEditComponents.js +38 -0
  384. package/src/services/helper/dateFormat.js +30 -0
  385. package/src/services/helper/formData.js +21 -0
  386. package/src/services/helper/getFieldByType.js +0 -0
  387. package/src/services/helper/getPropByString.js +18 -0
  388. package/src/services/helper/handleChange.js +26 -0
  389. package/src/services/helper/helper.js +105 -0
  390. package/src/services/helper/multiSelectEditor.js +226 -0
  391. package/src/services/helper/translateOrderReturnStatus.js +12 -0
  392. package/src/services/helper/translateRole.js +24 -0
  393. package/src/services/helper/translateStatus.js +12 -0
  394. package/src/services/helper/translateTransferReceiptType.js +67 -0
  395. package/src/services/helper/translsateStoreProductTransfer.js +40 -0
  396. package/src/services/helper/translsateStoreType.js +25 -0
  397. package/src/services/helper/useInterval.js +21 -0
  398. package/src/services/helper/yupCutomization.js +15 -0
  399. package/src/services/reportData/fetchReportData.js +210 -0
  400. package/src/views/ConfirmDialog.js +178 -0
  401. package/src/views/builder/JSEditor.js +226 -0
  402. package/src/views/builder/inspector/Inspector.jsx +63 -0
  403. package/src/views/builder/inspector/Tabs/ComponentActionsTab.jsx +117 -0
  404. package/src/views/builder/inspector/Tabs/MainTab.jsx +95 -0
  405. package/src/views/builder/inspector/Tabs/RulesTab.jsx +79 -0
  406. package/src/views/builder/inspector/Tabs/StyleTab.jsx +79 -0
  407. package/src/views/builder/inspector/definitions/autocomplete/main.js +25 -0
  408. package/src/views/builder/inspector/definitions/breadcrumb/main.js +9 -0
  409. package/src/views/builder/inspector/definitions/button/actions.js +12 -0
  410. package/src/views/builder/inspector/definitions/button/main.js +14 -0
  411. package/src/views/builder/inspector/definitions/button/rules.js +5 -0
  412. package/src/views/builder/inspector/definitions/button/style.js +32 -0
  413. package/src/views/builder/inspector/definitions/button.actions.js +12 -0
  414. package/src/views/builder/inspector/definitions/card/main.js +11 -0
  415. package/src/views/builder/inspector/definitions/cell/main.js +4 -0
  416. package/src/views/builder/inspector/definitions/checkbox/actions.js +1 -0
  417. package/src/views/builder/inspector/definitions/checkbox/main.js +13 -0
  418. package/src/views/builder/inspector/definitions/checkbox/rules.js +8 -0
  419. package/src/views/builder/inspector/definitions/checkbox/style.js +38 -0
  420. package/src/views/builder/inspector/definitions/checkboxFields.js +0 -0
  421. package/src/views/builder/inspector/definitions/column/main.js +9 -0
  422. package/src/views/builder/inspector/definitions/column-group/main.js +18 -0
  423. package/src/views/builder/inspector/definitions/common/actions.js +1 -0
  424. package/src/views/builder/inspector/definitions/common/main.js +14 -0
  425. package/src/views/builder/inspector/definitions/common/rules.js +8 -0
  426. package/src/views/builder/inspector/definitions/common/style.js +39 -0
  427. package/src/views/builder/inspector/definitions/common.advanced.js +8 -0
  428. package/src/views/builder/inspector/definitions/common.main.js +14 -0
  429. package/src/views/builder/inspector/definitions/common.style.js +33 -0
  430. package/src/views/builder/inspector/definitions/commonMainFields.js +18 -0
  431. package/src/views/builder/inspector/definitions/container/actions.js +1 -0
  432. package/src/views/builder/inspector/definitions/container/main.js +4 -0
  433. package/src/views/builder/inspector/definitions/container/rules.js +4 -0
  434. package/src/views/builder/inspector/definitions/container/style.js +32 -0
  435. package/src/views/builder/inspector/definitions/datepicker/actions.js +1 -0
  436. package/src/views/builder/inspector/definitions/datepicker/main.js +28 -0
  437. package/src/views/builder/inspector/definitions/datepicker/rules.js +8 -0
  438. package/src/views/builder/inspector/definitions/datepicker/style.js +38 -0
  439. package/src/views/builder/inspector/definitions/datepicker.main.js +28 -0
  440. package/src/views/builder/inspector/definitions/divider/actions.js +1 -0
  441. package/src/views/builder/inspector/definitions/divider/main.js +23 -0
  442. package/src/views/builder/inspector/definitions/divider/rules.js +4 -0
  443. package/src/views/builder/inspector/definitions/divider/style.js +9 -0
  444. package/src/views/builder/inspector/definitions/dropdown/actions.js +10 -0
  445. package/src/views/builder/inspector/definitions/dropdown/main.js +19 -0
  446. package/src/views/builder/inspector/definitions/dropdown/rules.js +8 -0
  447. package/src/views/builder/inspector/definitions/dropdown/style.js +38 -0
  448. package/src/views/builder/inspector/definitions/header/actions.js +1 -0
  449. package/src/views/builder/inspector/definitions/header/main.js +29 -0
  450. package/src/views/builder/inspector/definitions/header/rules.js +4 -0
  451. package/src/views/builder/inspector/definitions/header/style.js +12 -0
  452. package/src/views/builder/inspector/definitions/header-cell/main.js +5 -0
  453. package/src/views/builder/inspector/definitions/image/actions.js +1 -0
  454. package/src/views/builder/inspector/definitions/image/main.js +13 -0
  455. package/src/views/builder/inspector/definitions/image/rules.js +4 -0
  456. package/src/views/builder/inspector/definitions/image/style.js +12 -0
  457. package/src/views/builder/inspector/definitions/index.js +407 -0
  458. package/src/views/builder/inspector/definitions/input/actions.js +1 -0
  459. package/src/views/builder/inspector/definitions/input/main.js +14 -0
  460. package/src/views/builder/inspector/definitions/input/rules.js +8 -0
  461. package/src/views/builder/inspector/definitions/input/style.js +32 -0
  462. package/src/views/builder/inspector/definitions/label/actions.js +1 -0
  463. package/src/views/builder/inspector/definitions/label/main.js +24 -0
  464. package/src/views/builder/inspector/definitions/label/rules.js +4 -0
  465. package/src/views/builder/inspector/definitions/label/style.js +12 -0
  466. package/src/views/builder/inspector/definitions/layout/actions.js +1 -0
  467. package/src/views/builder/inspector/definitions/layout/main.js +6 -0
  468. package/src/views/builder/inspector/definitions/layout/rules.js +3 -0
  469. package/src/views/builder/inspector/definitions/layout/style.js +7 -0
  470. package/src/views/builder/inspector/definitions/layout-cell/actions.js +1 -0
  471. package/src/views/builder/inspector/definitions/layout-cell/main.js +7 -0
  472. package/src/views/builder/inspector/definitions/layout-cell/rules.js +3 -0
  473. package/src/views/builder/inspector/definitions/layout-cell/style.js +44 -0
  474. package/src/views/builder/inspector/definitions/link/actions.js +1 -0
  475. package/src/views/builder/inspector/definitions/link/main.js +29 -0
  476. package/src/views/builder/inspector/definitions/link/rules.js +4 -0
  477. package/src/views/builder/inspector/definitions/link/style.js +11 -0
  478. package/src/views/builder/inspector/definitions/menu/actions.js +3 -0
  479. package/src/views/builder/inspector/definitions/menu/main.js +16 -0
  480. package/src/views/builder/inspector/definitions/menu/rules.js +4 -0
  481. package/src/views/builder/inspector/definitions/menu/style.js +33 -0
  482. package/src/views/builder/inspector/definitions/number/actions.js +10 -0
  483. package/src/views/builder/inspector/definitions/number/main.js +29 -0
  484. package/src/views/builder/inspector/definitions/number/rules.js +8 -0
  485. package/src/views/builder/inspector/definitions/number/style.js +32 -0
  486. package/src/views/builder/inspector/definitions/progress-circle/actions.js +1 -0
  487. package/src/views/builder/inspector/definitions/progress-circle/main.js +18 -0
  488. package/src/views/builder/inspector/definitions/progress-circle/rules.js +4 -0
  489. package/src/views/builder/inspector/definitions/progress-circle/style.js +5 -0
  490. package/src/views/builder/inspector/definitions/progress-line/actions.js +1 -0
  491. package/src/views/builder/inspector/definitions/progress-line/main.js +16 -0
  492. package/src/views/builder/inspector/definitions/progress-line/rules.js +4 -0
  493. package/src/views/builder/inspector/definitions/progress-line/style.js +6 -0
  494. package/src/views/builder/inspector/definitions/radio/actions.js +10 -0
  495. package/src/views/builder/inspector/definitions/radio/main.js +16 -0
  496. package/src/views/builder/inspector/definitions/radio/rules.js +8 -0
  497. package/src/views/builder/inspector/definitions/radio/style.js +32 -0
  498. package/src/views/builder/inspector/definitions/reportViewer/main.js +12 -0
  499. package/src/views/builder/inspector/definitions/richtext/actions.js +10 -0
  500. package/src/views/builder/inspector/definitions/richtext/main.js +21 -0
  501. package/src/views/builder/inspector/definitions/richtext/rules.js +8 -0
  502. package/src/views/builder/inspector/definitions/richtext/style.js +32 -0
  503. package/src/views/builder/inspector/definitions/signature/actions.js +1 -0
  504. package/src/views/builder/inspector/definitions/signature/main.js +13 -0
  505. package/src/views/builder/inspector/definitions/signature/rules.js +8 -0
  506. package/src/views/builder/inspector/definitions/signature/style.js +26 -0
  507. package/src/views/builder/inspector/definitions/table/main.js +9 -0
  508. package/src/views/builder/inspector/definitions/tag/actions.js +10 -0
  509. package/src/views/builder/inspector/definitions/tag/main.js +17 -0
  510. package/src/views/builder/inspector/definitions/tag/rules.js +8 -0
  511. package/src/views/builder/inspector/definitions/tag/style.js +32 -0
  512. package/src/views/builder/inspector/definitions/textarea/actions.js +1 -0
  513. package/src/views/builder/inspector/definitions/textarea/main.js +16 -0
  514. package/src/views/builder/inspector/definitions/textarea/rules.js +8 -0
  515. package/src/views/builder/inspector/definitions/textarea/style.js +32 -0
  516. package/src/views/builder/inspector/definitions/time/actions.js +10 -0
  517. package/src/views/builder/inspector/definitions/time/main.js +18 -0
  518. package/src/views/builder/inspector/definitions/time/rules.js +8 -0
  519. package/src/views/builder/inspector/definitions/time/style.js +32 -0
  520. package/src/views/builder/inspector/definitions/toggle/actions.js +10 -0
  521. package/src/views/builder/inspector/definitions/toggle/main.js +26 -0
  522. package/src/views/builder/inspector/definitions/toggle/rules.js +8 -0
  523. package/src/views/builder/inspector/definitions/toggle/style.js +32 -0
  524. package/src/views/builder/inspector/fields/BooleanEditor.jsx +30 -0
  525. package/src/views/builder/inspector/fields/ExpressionEditor.jsx +35 -0
  526. package/src/views/builder/inspector/fields/FieldWrapper.jsx +15 -0
  527. package/src/views/builder/inspector/fields/ItemsEditor.jsx +118 -0
  528. package/src/views/builder/inspector/fields/OptionsEditor.jsx +118 -0
  529. package/src/views/builder/inspector/fields/SelectEditor.jsx +32 -0
  530. package/src/views/builder/inspector/fields/TabsEditor.jsx +128 -0
  531. package/src/views/builder/inspector/fields/TextEditor.jsx +28 -0
  532. package/src/views/builder/inspector/fields/TextFieldEditor.jsx +0 -0
  533. package/src/views/builder/sidebar/Sidebar.jsx +20 -0
  534. package/src/views/builder/sidebar/SidebarTabs.jsx +50 -0
  535. package/src/views/builder/sidebar/tabs/ActionsTab.jsx +94 -0
  536. package/src/views/builder/sidebar/tabs/Components/ComponentItem.jsx +69 -0
  537. package/src/views/builder/sidebar/tabs/Components/ComponentsTab.jsx +89 -0
  538. package/src/views/builder/sidebar/tabs/Components/GroupLevelAutocomplete.jsx +269 -0
  539. package/src/views/builder/sidebar/tabs/Components/componentCatalog.js +69 -0
  540. package/src/views/builder/sidebar/tabs/TreeTab.jsx +205 -0
  541. package/src/views/builder/sidebar/tabs/ViewTab.jsx +121 -0
  542. package/src/views/builder/viewer/Canvas.jsx +7 -0
  543. package/src/views/builder/viewer/ComponentRenderer.jsx +166 -0
  544. package/src/views/builder/viewer/DropZone.jsx +60 -0
  545. package/src/views/builder/viewer/ProductionViewer.jsx +155 -0
  546. package/src/views/builder/viewer/SaveViewDialog.jsx +60 -0
  547. package/src/views/builder/viewer/SaveWidgetDialog.jsx +142 -0
  548. package/src/views/builder/viewer/Viewer.jsx +83 -0
  549. package/src/views/builder/viewer/ViewerComponentWrapper.jsx +238 -0
  550. package/src/views/builder/viewer/ViewerToolbar.jsx +89 -0
  551. package/src/views/builder/viewer/renderers/AutoCompleteRenderer.jsx +57 -0
  552. package/src/views/builder/viewer/renderers/BreadcrumbRenderer.jsx +32 -0
  553. package/src/views/builder/viewer/renderers/ButtonRenderer.jsx +55 -0
  554. package/src/views/builder/viewer/renderers/CardRenderer.jsx +76 -0
  555. package/src/views/builder/viewer/renderers/CellRenderer.jsx +71 -0
  556. package/src/views/builder/viewer/renderers/CheckboxRenderer.jsx +27 -0
  557. package/src/views/builder/viewer/renderers/ColumnGroupRenderer.jsx +96 -0
  558. package/src/views/builder/viewer/renderers/ColumnRenderer.jsx +71 -0
  559. package/src/views/builder/viewer/renderers/ContainerRenderer.jsx +111 -0
  560. package/src/views/builder/viewer/renderers/DatePickerRenderer.jsx +49 -0
  561. package/src/views/builder/viewer/renderers/DividerRenderer.jsx +22 -0
  562. package/src/views/builder/viewer/renderers/DropdownRenderer.jsx +63 -0
  563. package/src/views/builder/viewer/renderers/HeaderCellRenderer.jsx +78 -0
  564. package/src/views/builder/viewer/renderers/HeaderRenderer.jsx +23 -0
  565. package/src/views/builder/viewer/renderers/ImageRenderer.jsx +26 -0
  566. package/src/views/builder/viewer/renderers/InputRenderer.jsx +34 -0
  567. package/src/views/builder/viewer/renderers/LabelRenderer.jsx +23 -0
  568. package/src/views/builder/viewer/renderers/LayoutCellRenderer.jsx +162 -0
  569. package/src/views/builder/viewer/renderers/LayoutContextMenu.jsx +173 -0
  570. package/src/views/builder/viewer/renderers/LayoutRenderer.jsx +51 -0
  571. package/src/views/builder/viewer/renderers/LinkRenderer.jsx +24 -0
  572. package/src/views/builder/viewer/renderers/MenuRenderer.jsx +291 -0
  573. package/src/views/builder/viewer/renderers/NumberFormatRenderer.jsx +80 -0
  574. package/src/views/builder/viewer/renderers/ProgressCircleRenderer.jsx +47 -0
  575. package/src/views/builder/viewer/renderers/ProgressLineRenderer.jsx +36 -0
  576. package/src/views/builder/viewer/renderers/RadioGroupRenderer.jsx +57 -0
  577. package/src/views/builder/viewer/renderers/RepeaterRenderer.jsx +94 -0
  578. package/src/views/builder/viewer/renderers/ReportViewerRenderer.jsx +15 -0
  579. package/src/views/builder/viewer/renderers/RichTextRenderer.jsx +76 -0
  580. package/src/views/builder/viewer/renderers/SignatureRenderer.jsx +89 -0
  581. package/src/views/builder/viewer/renderers/TabRenderer.jsx +82 -0
  582. package/src/views/builder/viewer/renderers/TableRenderer.jsx +92 -0
  583. package/src/views/builder/viewer/renderers/TagPickerRenderer.jsx +67 -0
  584. package/src/views/builder/viewer/renderers/TextAreaRenderer.jsx +37 -0
  585. package/src/views/builder/viewer/renderers/TextRenderer.jsx +9 -0
  586. package/src/views/builder/viewer/renderers/TimePickerRenderer.jsx +49 -0
  587. package/src/views/builder/viewer/renderers/ToggleRenderer.jsx +46 -0
  588. package/src/views/builder/viewer/renderers/WizardRenderer.jsx +88 -0
  589. package/src/views/builder/viewer/renderers/WizardStepRenderer.jsx +72 -0
  590. package/src/views/customFilter/CustomFilterDialog.js +1142 -0
  591. package/src/views/genericTable/BuilderExpressionParams.js +193 -0
  592. package/src/views/genericTable/FixedFilterDialog.js +447 -0
  593. package/src/views/genericTable/GenericForm.js +301 -0
  594. package/src/views/genericTable/QueryEditor.js +99 -0
  595. package/src/views/genericTable/RegexTextEditor.js +182 -0
  596. package/src/views/genericTable/ReportBuilderSaveDialog.js +153 -0
  597. package/src/views/genericTable/RoutingSettingDialog.js +189 -0
  598. package/src/views/genericTable/SGrid.js +2168 -0
  599. package/src/views/genericTable/SearchFilterDialog.js +247 -0
  600. package/src/views/genericTable/TAGGrid.js +1046 -0
  601. package/src/views/genericTable/cellEditors/autocompleteEditor.js +229 -0
  602. package/src/views/genericTable/cellRenderers/imageRenderer.js +14 -0
  603. package/src/views/genericTable/statusBar/rowCountStatusBar.js +37 -0
  604. package/src/views/genericTable/template/addTemplate.js +187 -0
  605. package/src/views/genericTable/toolPanels/CustomColumnsToolPanel.js +43 -0
  606. package/src/views/pages/auth/FooterIllustrationsV2.js +40 -0
  607. package/src/views/pages/misc/FooterIllustrations.js +47 -0
  608. package/src/views/pages/misc/muiTable/CustomPagination.js +34 -0
  609. package/src/views/pages/users/UserManageDialog.js +283 -0
  610. package/src/views/pages/users/UserViewPage.js +199 -0
  611. package/src/views/users/AddUserNameDialog.js +162 -0
  612. package/src/views/users/ContactManage.js +449 -0
  613. package/src/views/users/ResetPasswordDialog.js +242 -0
  614. package/styles/globals.css +71 -0
@@ -0,0 +1,2070 @@
1
+ 'use client'
2
+
3
+ import React, { createContext, useContext, useReducer, useCallback, useMemo, useState, useEffect } from 'react'
4
+ import {
5
+ DndContext,
6
+ closestCenter,
7
+ KeyboardSensor,
8
+ PointerSensor,
9
+ useSensor,
10
+ useSensors,
11
+ DragOverlay
12
+ } from '@dnd-kit/core'
13
+ import {
14
+ arrayMove,
15
+ SortableContext,
16
+ sortableKeyboardCoordinates,
17
+ useSortable,
18
+ verticalListSortingStrategy
19
+ } from '@dnd-kit/sortable'
20
+ import { CSS } from '@dnd-kit/utilities'
21
+ import {
22
+ Box,
23
+ Paper,
24
+ Typography,
25
+ Button,
26
+ TextField,
27
+ IconButton,
28
+ List,
29
+ ListItem,
30
+ ListItemText,
31
+ ListItemButton,
32
+ ListItemIcon,
33
+ Divider as MuiDivider,
34
+ Chip,
35
+ Alert,
36
+ Dialog,
37
+ DialogTitle,
38
+ DialogContent,
39
+ DialogActions,
40
+ Select,
41
+ MenuItem,
42
+ FormControl,
43
+ InputLabel,
44
+ Tooltip,
45
+ Stack,
46
+ Tabs,
47
+ Tab,
48
+ Collapse
49
+ } from '@mui/material'
50
+ import {
51
+ Add as AddIcon,
52
+ Delete as DeleteIcon,
53
+ Edit as EditIcon,
54
+ ContentCopy as CloneIcon,
55
+ ArrowUpward as MoveUpIcon,
56
+ ArrowDownward as MoveDownIcon,
57
+ ArrowBack as MoveOutIcon,
58
+ ArrowForward as MoveInIcon,
59
+ DragIndicator as DragIcon,
60
+ Save as SaveIcon,
61
+ FolderOpen as LoadIcon,
62
+ CheckCircle as ValidIcon,
63
+ Error as ErrorIcon,
64
+ Label as ItemIcon,
65
+ Folder as GroupIcon,
66
+ Remove as DividerIcon,
67
+ Link as ExternalIcon,
68
+ Visibility as PreviewIcon,
69
+ ChevronRight as ChevronRightIcon,
70
+ } from '@mui/icons-material'
71
+ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
72
+ import * as Icons from 'mdi-material-ui'
73
+ import { v4 as uuidv4 } from 'uuid'
74
+ import PostService from 'src/services/PostService'
75
+ import GetService from 'src/services/GetService'
76
+ import { Endpoints } from 'src/services/Endpoints'
77
+
78
+ const NODE_TYPES = {
79
+ ITEM: 'item',
80
+ DIVIDER: 'divider',
81
+ EXTERNAL: 'external'
82
+ }
83
+
84
+ const NAVIGATION_TYPES = {
85
+ STATIC: 'static', // Static path (hardcoded)
86
+ DYNAMIC: 'dynamic', // Dynamic viewId (from registry)
87
+ EXTERNAL: 'external' // External URL
88
+ }
89
+
90
+ // Create a navigation item (becomes group if it has children)
91
+ const createNavItem = () => ({
92
+ id: uuidv4(),
93
+ title: 'New Item',
94
+ icon: null,
95
+ type: NAVIGATION_TYPES.DYNAMIC, // Default to dynamic (viewId)
96
+ viewId: null, // For dynamic type
97
+ path: null, // For static type
98
+ externalUrl: null, // For external type
99
+ children: [] // ✅ ALWAYS array
100
+ })
101
+
102
+ // Create the default mainHome root group
103
+ const createMainHomeRoot = () => ({
104
+ id: 'mainHome-root',
105
+ title: 'mainHome',
106
+ icon: null,
107
+ type: null, // Root group has no type
108
+ viewId: null,
109
+ path: null,
110
+ externalUrl: null,
111
+ children: [] // ✅ ALWAYS array
112
+ })
113
+
114
+ /**
115
+ * Navigation Config - the single output
116
+ * Always has a mainHome root group
117
+ */
118
+ const createEmptyNavConfig = () => ({
119
+ id: uuidv4(),
120
+ name: 'New Navigation',
121
+ items: [createMainHomeRoot()], // Always start with mainHome root
122
+ createdAt: new Date().toISOString(),
123
+ updatedAt: new Date().toISOString()
124
+ })
125
+
126
+ // ============================================================================
127
+ // STEP 3: TREE OPERATIONS
128
+ // ============================================================================
129
+
130
+ /**
131
+ * Find node in tree by ID
132
+ */
133
+ const findNodeInTree = (tree, nodeId) => {
134
+ if (!tree || !Array.isArray(tree)) return null
135
+
136
+ for (const node of tree) {
137
+ if (node.id === nodeId) return node
138
+ if (node.children && Array.isArray(node.children)) {
139
+ const found = findNodeInTree(node.children, nodeId)
140
+ if (found) return found
141
+ }
142
+ }
143
+ return null
144
+ }
145
+
146
+ const findParent = (tree, nodeId, parent = null) => {
147
+ if (!tree || !Array.isArray(tree)) return null
148
+
149
+ for (const node of tree) {
150
+ if (node.id === nodeId) return parent
151
+ if (node.children && Array.isArray(node.children)) {
152
+ const found = findParent(node.children, nodeId, node)
153
+ if (found !== null) return found
154
+ }
155
+ }
156
+ return null
157
+ }
158
+
159
+ const removeNodeFromTree = (tree, nodeId) => {
160
+ if (!tree || !Array.isArray(tree)) return tree
161
+
162
+ return tree
163
+ .filter(node => node.id !== nodeId)
164
+ .map(node => ({
165
+ ...node,
166
+ children: Array.isArray(node.children)
167
+ ? removeNodeFromTree(node.children, nodeId)
168
+ : []
169
+ }))
170
+ }
171
+
172
+ const updateNodeInTree = (tree, nodeId, updates) => {
173
+ if (!tree || !Array.isArray(tree)) return tree
174
+
175
+ return tree.map(node => {
176
+ if (node.id === nodeId) {
177
+ // Ensure children is always an array - NEVER undefined or null
178
+ const updatedNode = { ...node, ...updates, updatedAt: new Date().toISOString() }
179
+ if (!Array.isArray(updatedNode.children)) {
180
+ updatedNode.children = []
181
+ }
182
+ return updatedNode
183
+ }
184
+ if (Array.isArray(node.children)) {
185
+ return {
186
+ ...node,
187
+ children: updateNodeInTree(node.children, nodeId, updates)
188
+ }
189
+ }
190
+ // Always return children as array
191
+ return { ...node, children: [] }
192
+ })
193
+ }
194
+
195
+ const addNodeToTree = (tree, parentId, newNode, position = 'end') => {
196
+ if (!tree || !Array.isArray(tree)) return tree
197
+
198
+ if (parentId === null) {
199
+ const newTree = [...tree]
200
+ if (position === 'start') {
201
+ newTree.unshift(newNode)
202
+ } else {
203
+ newTree.push(newNode)
204
+ }
205
+ return newTree.map((node, index) => ({ ...node, order: index }))
206
+ }
207
+
208
+ return tree.map(node => {
209
+ if (node.id === parentId) {
210
+ // Create a new node object, don't mutate the original
211
+ // Ensure children is always an array
212
+ const existingChildren = Array.isArray(node.children) ? node.children : []
213
+ const newChildren = [...existingChildren]
214
+ if (position === 'start') {
215
+ newChildren.unshift(newNode)
216
+ } else {
217
+ newChildren.push(newNode)
218
+ }
219
+ return {
220
+ ...node,
221
+ children: newChildren.map((child, index) => ({ ...child, order: index }))
222
+ }
223
+ }
224
+ if (Array.isArray(node.children)) {
225
+ return {
226
+ ...node,
227
+ children: addNodeToTree(node.children, parentId, newNode, position)
228
+ }
229
+ }
230
+ // Always return children as array
231
+ return { ...node, children: [] }
232
+ })
233
+ }
234
+
235
+
236
+ const cloneNode = (node) => {
237
+ const cloned = {
238
+ ...node,
239
+ id: uuidv4(),
240
+ title: `${node.title || 'Untitled'} (Copy)`,
241
+ order: 0
242
+ }
243
+ if (Array.isArray(cloned.children) && cloned.children.length > 0) {
244
+ cloned.children = cloned.children.map(child => cloneNode(child))
245
+ } else {
246
+ cloned.children = []
247
+ }
248
+ return cloned
249
+ }
250
+
251
+ /**
252
+ * Reorder nodes using drag and drop
253
+ */
254
+ const reorderNodesInTree = (tree, source, destination) => {
255
+ if (!tree || !Array.isArray(tree)) return tree
256
+ if (!destination) return tree
257
+
258
+ // Helper to find node by ID
259
+ const findNodeById = (nodes, nodeId) => {
260
+ for (const node of nodes) {
261
+ if (node.id === nodeId) return node
262
+ if (node.children) {
263
+ const found = findNodeById(node.children, nodeId)
264
+ if (found) return found
265
+ }
266
+ }
267
+ return null
268
+ }
269
+
270
+ // Helper to update children of a node
271
+ const updateNodeChildren = (nodes, nodeId, newChildren) => {
272
+ return nodes.map(node => {
273
+ if (node.id === nodeId) {
274
+ return { ...node, children: newChildren }
275
+ }
276
+ if (node.children) {
277
+ return { ...node, children: updateNodeChildren(node.children, nodeId, newChildren) }
278
+ }
279
+ return node
280
+ })
281
+ }
282
+
283
+ // Get the node being moved
284
+ const nodeToMove = source.droppableId === 'root'
285
+ ? tree[source.index]
286
+ : (findNodeById(tree, source.droppableId)?.children?.[source.index])
287
+
288
+ if (!nodeToMove) return tree
289
+
290
+ // Remove from source
291
+ let result = tree
292
+ if (source.droppableId === 'root') {
293
+ result = tree.filter((_, idx) => idx !== source.index)
294
+ } else {
295
+ const parent = findNodeById(tree, source.droppableId)
296
+ if (parent && parent.children) {
297
+ const newChildren = parent.children.filter((_, idx) => idx !== source.index)
298
+ result = updateNodeChildren(tree, source.droppableId, newChildren)
299
+ }
300
+ }
301
+
302
+ // Add to destination
303
+ if (destination.droppableId === 'root') {
304
+ const newRoot = [...result]
305
+ newRoot.splice(destination.index, 0, nodeToMove)
306
+ result = newRoot
307
+ } else {
308
+ const parent = findNodeById(result, destination.droppableId)
309
+ if (parent) {
310
+ const newChildren = [...(parent.children || [])]
311
+ newChildren.splice(destination.index, 0, nodeToMove)
312
+ result = updateNodeChildren(result, destination.droppableId, newChildren)
313
+ }
314
+ }
315
+
316
+ return result
317
+ }
318
+
319
+ const moveNodeInTree = (tree, nodeId, direction) => {
320
+ if (!tree || !Array.isArray(tree)) return tree
321
+
322
+ const findSiblings = (nodes, targetId, parent = null) => {
323
+ for (let i = 0; i < nodes.length; i++) {
324
+ if (nodes[i].id === targetId) {
325
+ return { siblings: nodes, index: i, parent }
326
+ }
327
+ if (nodes[i].children) {
328
+ const found = findSiblings(nodes[i].children, targetId, nodes[i])
329
+ if (found) return found
330
+ }
331
+ }
332
+ return null
333
+ }
334
+
335
+ const result = findSiblings(tree, nodeId)
336
+ if (!result) return tree
337
+
338
+ const { siblings, index, parent } = result
339
+ const newSiblings = [...siblings]
340
+
341
+ if (direction === 'up' && index > 0) {
342
+ [newSiblings[index - 1], newSiblings[index]] = [newSiblings[index], newSiblings[index - 1]]
343
+ } else if (direction === 'down' && index < newSiblings.length - 1) {
344
+ [newSiblings[index], newSiblings[index + 1]] = [newSiblings[index + 1], newSiblings[index]]
345
+ } else if (direction === 'in' && index > 0) {
346
+ // Move as child of previous sibling
347
+ const prevSibling = newSiblings[index - 1]
348
+ if (prevSibling.type === NODE_TYPES.GROUP) {
349
+ const nodeToMove = newSiblings[index]
350
+ newSiblings.splice(index, 1)
351
+ prevSibling.children = [...(prevSibling.children || []), nodeToMove]
352
+ }
353
+ } else if (direction === 'out' && parent) {
354
+ const nodeToMove = newSiblings[index]
355
+ newSiblings.splice(index, 1)
356
+ }
357
+
358
+ newSiblings.forEach((node, idx) => {
359
+ node.order = idx
360
+ })
361
+
362
+ const reconstructTree = (nodes) => {
363
+ return nodes.map(node => {
364
+ if (parent && node.id === parent.id) {
365
+ return { ...node, children: newSiblings }
366
+ }
367
+ if (node.children) {
368
+ return { ...node, children: reconstructTree(node.children) }
369
+ }
370
+ return node
371
+ })
372
+ }
373
+
374
+ return parent ? reconstructTree(tree) : newSiblings
375
+ }
376
+
377
+
378
+ const validateNavigation = (navConfig) => {
379
+ const errors = []
380
+ const warnings = []
381
+ const nodeIds = new Set()
382
+
383
+ const validateNode = (node, path = []) => {
384
+ const currentPath = [...path, node.id]
385
+
386
+ // Check for duplicate IDs
387
+ if (nodeIds.has(node.id)) {
388
+ errors.push({
389
+ nodeId: node.id,
390
+ message: `Duplicate ID: ${node.id}`,
391
+ path: currentPath
392
+ })
393
+ }
394
+ nodeIds.add(node.id)
395
+
396
+ // Validate navigation item
397
+ // Single source of truth: Items with children are groups, items without children are simple nav items
398
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0
399
+
400
+ if (!node.title || (typeof node.title === 'string' && node.title.trim() === '')) {
401
+ errors.push({
402
+ nodeId: node.id,
403
+ message: 'Navigation item must have a title',
404
+ path: currentPath
405
+ })
406
+ }
407
+
408
+ // Leaf must have appropriate field based on type
409
+ if (!hasChildren) {
410
+ const navType = node.type || NAVIGATION_TYPES.DYNAMIC
411
+
412
+ if (navType === NAVIGATION_TYPES.DYNAMIC) {
413
+ if (node.viewId === null || node.viewId === undefined || node.viewId === '') {
414
+ errors.push({
415
+ nodeId: node.id,
416
+ message: 'Dynamic navigation item must have a viewId',
417
+ path: currentPath
418
+ })
419
+ }
420
+ } else if (navType === NAVIGATION_TYPES.STATIC) {
421
+ if (!node.path || (typeof node.path === 'string' && node.path.trim() === '')) {
422
+ errors.push({
423
+ nodeId: node.id,
424
+ message: 'Static navigation item must have a path',
425
+ path: currentPath
426
+ })
427
+ }
428
+ } else if (navType === NAVIGATION_TYPES.EXTERNAL) {
429
+ if (!node.externalUrl || (typeof node.externalUrl === 'string' && node.externalUrl.trim() === '')) {
430
+ errors.push({
431
+ nodeId: node.id,
432
+ message: 'External navigation item must have an externalUrl',
433
+ path: currentPath
434
+ })
435
+ }
436
+ }
437
+ }
438
+
439
+ // Validate children recursively
440
+ if (hasChildren) {
441
+ node.children.forEach(child => validateNode(child, currentPath))
442
+ }
443
+ }
444
+
445
+ // Validate root structure
446
+ if (!navConfig.items || !Array.isArray(navConfig.items)) {
447
+ errors.push({
448
+ nodeId: 'root',
449
+ message: 'Navigation must have items array',
450
+ path: []
451
+ })
452
+ } else {
453
+ navConfig.items.forEach(item => validateNode(item, []))
454
+ }
455
+
456
+ return {
457
+ isValid: errors.length === 0,
458
+ errors,
459
+ warnings
460
+ }
461
+ }
462
+
463
+ // ============================================================================
464
+ // STEP 4: REDUCER & STATE MANAGEMENT
465
+ // ============================================================================
466
+
467
+ const initialState = {
468
+ navConfig: createEmptyNavConfig(),
469
+ selectedNodeId: null,
470
+ expandedNodes: [], // Track which nodes are expanded (array of IDs)
471
+ validation: { isValid: true, errors: [], warnings: [] },
472
+ isDirty: false,
473
+ registry: {} // Dynamic registry loaded from server
474
+ }
475
+
476
+ const navigatorReducer = (state, action) => {
477
+ switch (action.type) {
478
+ case 'CREATE_NAVIGATION':
479
+ return {
480
+ ...initialState,
481
+ navConfig: createEmptyNavConfig()
482
+ }
483
+
484
+ case 'LOAD_NAVIGATION':
485
+ // Migration: convert old format (path/action/subject) to new format (viewId/type)
486
+ const migrate = (items) =>
487
+ items.map(i => {
488
+ const migrated = {
489
+ ...i,
490
+ // Ensure ID exists (generate if missing)
491
+ id: i.id || (i.title === 'mainHome' ? 'mainHome-root' : uuidv4()),
492
+ // Set default type if missing
493
+ type: i.type || (i.viewId ? NAVIGATION_TYPES.DYNAMIC : i.path ? NAVIGATION_TYPES.STATIC : null),
494
+ viewId: i.viewId ?? null,
495
+ path: i.path ?? null,
496
+ externalUrl: i.externalUrl ?? null,
497
+ children: Array.isArray(i.children) ? migrate(i.children) : []
498
+ }
499
+ // Remove old keys
500
+ delete migrated.action
501
+ delete migrated.subject
502
+ return migrated
503
+ })
504
+
505
+ const loadedConfig = action.payload.config || action.payload
506
+ const loadedRegistry = action.payload.registry || {}
507
+ console.log('LOAD_NAVIGATION reducer - received config:', loadedConfig)
508
+ console.log('LOAD_NAVIGATION reducer - received registry:', loadedRegistry)
509
+
510
+ if (loadedConfig.items) {
511
+ console.log('Migrating items, count:', loadedConfig.items.length)
512
+ loadedConfig.items = migrate(loadedConfig.items)
513
+ console.log('After migration:', loadedConfig.items)
514
+
515
+ // Ensure mainHome root exists
516
+ // Check if first item is mainHome root
517
+ const hasMainHomeRoot = loadedConfig.items.length > 0 &&
518
+ (loadedConfig.items[0].id === 'mainHome-root' ||
519
+ loadedConfig.items[0].title === 'mainHome')
520
+
521
+ console.log('Has mainHome root?', hasMainHomeRoot)
522
+
523
+ if (!hasMainHomeRoot) {
524
+ // Wrap existing items in mainHome root
525
+ console.log('Wrapping items in mainHome root')
526
+ const mainHomeRoot = createMainHomeRoot()
527
+ mainHomeRoot.children = loadedConfig.items
528
+ loadedConfig.items = [mainHomeRoot]
529
+ } else {
530
+ // Ensure the mainHome root has the correct ID
531
+ if (loadedConfig.items[0].title === 'mainHome' && loadedConfig.items[0].id !== 'mainHome-root') {
532
+ loadedConfig.items[0].id = 'mainHome-root'
533
+ }
534
+ // If mainHome root exists but has no children, ensure it has children array
535
+ if (!Array.isArray(loadedConfig.items[0].children)) {
536
+ loadedConfig.items[0].children = []
537
+ }
538
+ }
539
+ } else {
540
+ // No items, create empty mainHome root
541
+ console.log('No items found, creating empty mainHome root')
542
+ loadedConfig.items = [createMainHomeRoot()]
543
+ }
544
+
545
+ console.log('Final loaded config items:', loadedConfig.items)
546
+
547
+ const validation = validateNavigation(loadedConfig)
548
+ return {
549
+ ...state,
550
+ navConfig: loadedConfig,
551
+ registry: loadedRegistry,
552
+ selectedNodeId: null,
553
+ validation,
554
+ isDirty: false
555
+ }
556
+
557
+ case 'SELECT_NODE':
558
+ return {
559
+ ...state,
560
+ selectedNodeId: action.payload
561
+ }
562
+
563
+ case 'TOGGLE_EXPAND':
564
+ const nodeId = action.payload
565
+ const isCurrentlyExpanded = state.expandedNodes.includes(nodeId)
566
+ return {
567
+ ...state,
568
+ expandedNodes: isCurrentlyExpanded
569
+ ? state.expandedNodes.filter(id => id !== nodeId)
570
+ : [...state.expandedNodes, nodeId]
571
+ }
572
+
573
+ case 'ADD_NODE': {
574
+ const { parentId, position } = action.payload
575
+ const newNode = createNavItem()
576
+ // newNode.viewId stays null by default (acts like a group until you assign it)
577
+
578
+ const updatedItems = addNodeToTree(
579
+ state.navConfig.items,
580
+ parentId,
581
+ newNode,
582
+ position
583
+ )
584
+ const updatedConfig = {
585
+ ...state.navConfig,
586
+ items: updatedItems,
587
+ updatedAt: new Date().toISOString()
588
+ }
589
+ const newValidation = validateNavigation(updatedConfig)
590
+
591
+ // Auto-expand parent if adding a child - use Set to avoid duplicates
592
+ const expandedNodes = parentId
593
+ ? Array.from(new Set([...state.expandedNodes, parentId]))
594
+ : state.expandedNodes
595
+
596
+ return {
597
+ ...state,
598
+ navConfig: updatedConfig,
599
+ selectedNodeId: newNode.id,
600
+ expandedNodes,
601
+ validation: newValidation,
602
+ isDirty: true
603
+ }
604
+ }
605
+
606
+ case 'UPDATE_NODE': {
607
+ const { nodeId, updates } = action.payload
608
+ const updatedItems = updateNodeInTree(
609
+ state.navConfig.items,
610
+ nodeId,
611
+ updates
612
+ )
613
+ const updatedConfig = {
614
+ ...state.navConfig,
615
+ items: updatedItems,
616
+ updatedAt: new Date().toISOString()
617
+ }
618
+ const newValidation = validateNavigation(updatedConfig)
619
+
620
+ return {
621
+ ...state,
622
+ navConfig: updatedConfig,
623
+ validation: newValidation,
624
+ isDirty: true
625
+ }
626
+ }
627
+
628
+ case 'DELETE_NODE': {
629
+ // Prevent deleting the mainHome root
630
+ if (action.payload === 'mainHome-root') {
631
+ return state
632
+ }
633
+
634
+ const updatedItems = removeNodeFromTree(
635
+ state.navConfig.items,
636
+ action.payload
637
+ )
638
+ const updatedConfig = {
639
+ ...state.navConfig,
640
+ items: updatedItems,
641
+ updatedAt: new Date().toISOString()
642
+ }
643
+ const newValidation = validateNavigation(updatedConfig)
644
+
645
+ return {
646
+ ...state,
647
+ navConfig: updatedConfig,
648
+ selectedNodeId: state.selectedNodeId === action.payload ? null : state.selectedNodeId,
649
+ validation: newValidation,
650
+ isDirty: true
651
+ }
652
+ }
653
+
654
+ case 'CLONE_NODE': {
655
+ const node = findNodeInTree(state.navConfig.items, action.payload)
656
+ if (!node) return state
657
+
658
+ const cloned = {
659
+ ...node,
660
+ id: uuidv4(),
661
+ title: `${node.title} (Copy)`
662
+ }
663
+ if (cloned.children && Array.isArray(cloned.children)) {
664
+ const cloneChild = (child) => ({
665
+ ...child,
666
+ id: uuidv4()
667
+ })
668
+ cloned.children = cloned.children.map(cloneChild)
669
+ }
670
+
671
+ const parent = findParent(state.navConfig.items, action.payload)
672
+ const parentId = parent ? parent.id : null
673
+
674
+ const updatedItems = addNodeToTree(
675
+ state.navConfig.items,
676
+ parentId,
677
+ cloned,
678
+ 'end'
679
+ )
680
+ const updatedConfig = {
681
+ ...state.navConfig,
682
+ items: updatedItems,
683
+ updatedAt: new Date().toISOString()
684
+ }
685
+ const newValidation = validateNavigation(updatedConfig)
686
+
687
+ return {
688
+ ...state,
689
+ navConfig: updatedConfig,
690
+ selectedNodeId: cloned.id,
691
+ validation: newValidation,
692
+ isDirty: true
693
+ }
694
+ }
695
+
696
+ case 'MOVE_NODE': {
697
+ const { nodeId, direction } = action.payload
698
+ const updatedItems = moveNodeInTree(
699
+ state.navConfig.items,
700
+ nodeId,
701
+ direction
702
+ )
703
+ const updatedConfig = {
704
+ ...state.navConfig,
705
+ items: updatedItems,
706
+ updatedAt: new Date().toISOString()
707
+ }
708
+ const newValidation = validateNavigation(updatedConfig)
709
+
710
+ return {
711
+ ...state,
712
+ navConfig: updatedConfig,
713
+ validation: newValidation,
714
+ isDirty: true
715
+ }
716
+ }
717
+
718
+ case 'REORDER_NODES': {
719
+ const { source, destination } = action.payload
720
+ const updatedItems = reorderNodesInTree(
721
+ state.navConfig.items,
722
+ source,
723
+ destination
724
+ )
725
+ const updatedConfig = {
726
+ ...state.navConfig,
727
+ items: updatedItems,
728
+ updatedAt: new Date().toISOString()
729
+ }
730
+ const newValidation = validateNavigation(updatedConfig)
731
+
732
+ return {
733
+ ...state,
734
+ navConfig: updatedConfig,
735
+ validation: newValidation,
736
+ isDirty: true
737
+ }
738
+ }
739
+
740
+ case 'VALIDATE':
741
+ return {
742
+ ...state,
743
+ validation: validateNavigation(state.navConfig)
744
+ }
745
+
746
+ case 'SET_DIRTY':
747
+ return {
748
+ ...state,
749
+ isDirty: action.payload
750
+ }
751
+
752
+ default:
753
+ return state
754
+ }
755
+ }
756
+
757
+ // ============================================================================
758
+ // CONTEXT
759
+ // ============================================================================
760
+
761
+ const NavigatorContext = createContext(null)
762
+
763
+ export const NavigatorProvider = ({ children }) => {
764
+ const [state, dispatch] = useReducer(navigatorReducer, initialState)
765
+
766
+ const actions = useMemo(() => ({
767
+ createNavigation: () => dispatch({ type: 'CREATE_NAVIGATION' }),
768
+ loadNavigation: (data) => dispatch({ type: 'LOAD_NAVIGATION', payload: data }),
769
+ selectNode: (nodeId) => dispatch({ type: 'SELECT_NODE', payload: nodeId }),
770
+ toggleExpand: (nodeId) => dispatch({ type: 'TOGGLE_EXPAND', payload: nodeId }),
771
+ addNode: (parentId, position = 'end') =>
772
+ dispatch({ type: 'ADD_NODE', payload: { parentId, position } }),
773
+ updateNode: (nodeId, updates) =>
774
+ dispatch({ type: 'UPDATE_NODE', payload: { nodeId, updates } }),
775
+ deleteNode: (nodeId) => dispatch({ type: 'DELETE_NODE', payload: nodeId }),
776
+ cloneNode: (nodeId) => dispatch({ type: 'CLONE_NODE', payload: nodeId }),
777
+ moveNode: (nodeId, direction) =>
778
+ dispatch({ type: 'MOVE_NODE', payload: { nodeId, direction } }),
779
+ reorderNodes: (source, destination) =>
780
+ dispatch({ type: 'REORDER_NODES', payload: { source, destination } }),
781
+ validate: () => dispatch({ type: 'VALIDATE' }),
782
+ getNavConfig: () => {
783
+ // Convert to navigation format: remove internal fields, keep only navigation structure
784
+ // Handle different navigation types (static, dynamic, external)
785
+ const convertToNavFormat = (items) => {
786
+ return items.map(item => {
787
+ const navItem = {
788
+ title: item.title,
789
+ icon: item.icon
790
+ }
791
+
792
+ const hasChildren = Array.isArray(item.children) && item.children.length > 0
793
+ const navType = item.type || NAVIGATION_TYPES.DYNAMIC
794
+
795
+ // Handle based on type (for both items and groups)
796
+ if (navType === NAVIGATION_TYPES.DYNAMIC && item.viewId) {
797
+ // Dynamic items/groups: always include viewId
798
+ navItem.type = 'dynamic'
799
+ navItem.viewId = item.viewId
800
+
801
+ // Only include explicit path if provided
802
+ // DO NOT generate /viewer/${viewId} path - let runtime resolve from registry
803
+ if (item.path) {
804
+ navItem.path = item.path
805
+ }
806
+ // Note: Path will be resolved at runtime from registry using viewId
807
+ } else if (navType === NAVIGATION_TYPES.STATIC && item.path && !hasChildren) {
808
+ // Static: use the path directly (only for items, not groups)
809
+ navItem.path = item.path
810
+ navItem.type = 'static'
811
+ } else if (navType === NAVIGATION_TYPES.EXTERNAL && item.externalUrl && !hasChildren) {
812
+ // External: use externalUrl (only for items, not groups)
813
+ navItem.externalUrl = item.externalUrl
814
+ navItem.type = 'external'
815
+ navItem.openInNewTab = true // External links typically open in new tab
816
+ }
817
+
818
+ // Add children if they exist (makes it a group)
819
+ if (hasChildren) {
820
+ navItem.children = convertToNavFormat(item.children)
821
+ }
822
+
823
+ return navItem
824
+ })
825
+ }
826
+
827
+ // Ensure mainHome root exists and extract its children for output
828
+ let itemsToConvert = state.navConfig.items
829
+
830
+ // If mainHome root exists, use its children; otherwise use items as-is
831
+ const mainHomeRoot = itemsToConvert.find(item =>
832
+ item.id === 'mainHome-root' || item.title === 'mainHome'
833
+ )
834
+
835
+ if (mainHomeRoot && Array.isArray(mainHomeRoot.children)) {
836
+ // Return mainHome with its children
837
+ itemsToConvert = [{
838
+ title: 'mainHome',
839
+ children: convertToNavFormat(mainHomeRoot.children)
840
+ }]
841
+ } else {
842
+ // No mainHome root, wrap items in mainHome
843
+ itemsToConvert = [{
844
+ title: 'mainHome',
845
+ children: convertToNavFormat(state.navConfig.items)
846
+ }]
847
+ }
848
+
849
+ return {
850
+ ...state.navConfig,
851
+ items: itemsToConvert
852
+ }
853
+ }
854
+ }), [state.navConfig])
855
+
856
+ return (
857
+ <NavigatorContext.Provider value={{ state, actions }}>
858
+ {children}
859
+ </NavigatorContext.Provider>
860
+ )
861
+ }
862
+
863
+ export const useNavigator = () => {
864
+ const context = useContext(NavigatorContext)
865
+ if (!context) {
866
+ throw new Error('useNavigator must be used within NavigatorProvider')
867
+ }
868
+ return context
869
+ }
870
+
871
+ // ============================================================================
872
+ // UI COMPONENTS
873
+ // ============================================================================
874
+
875
+
876
+ /**
877
+ * Tree Node Component - Using @dnd-kit useSortable
878
+ */
879
+ const TreeNode = ({ node, depth = 0 }) => {
880
+ const { state, actions } = useNavigator()
881
+
882
+ // Safety check
883
+ if (!node || !node.id) {
884
+ return null
885
+ }
886
+
887
+ // Debug logging (temporary)
888
+ // Debug logging (temporary) - remove after testing
889
+ console.log('NODE', node.title, 'children:', node.children?.length || 0)
890
+
891
+ const isSelected = state.selectedNodeId === node.id
892
+ // Single source of truth: group = has children array with items
893
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0
894
+ const isExpanded = state.expandedNodes.includes(node.id)
895
+ // Always show chevron if children array exists (even if empty)
896
+ const canHaveChildren = Array.isArray(node.children)
897
+
898
+ // @dnd-kit useSortable hook
899
+ const {
900
+ attributes,
901
+ listeners,
902
+ setNodeRef,
903
+ transform,
904
+ transition,
905
+ isDragging
906
+ } = useSortable({
907
+ id: String(node.id),
908
+ disabled: false
909
+ })
910
+
911
+ const style = {
912
+ transform: CSS.Transform.toString(transform),
913
+ transition,
914
+ opacity: isDragging ? 0.5 : 1
915
+ }
916
+
917
+ const handleToggleExpand = (e) => {
918
+ e.preventDefault()
919
+ e.stopPropagation()
920
+ actions.toggleExpand(node.id)
921
+ }
922
+
923
+ const getNodeIcon = () => {
924
+ // Use hasChildren (not isGroup) - single source of truth
925
+ if (hasChildren) {
926
+ return GroupIcon ? <GroupIcon fontSize="small" /> : null
927
+ }
928
+ return ItemIcon ? <ItemIcon fontSize="small" /> : null
929
+ }
930
+
931
+ const getPathDisplay = () => {
932
+ if (hasChildren) return `${node.children.length} items`
933
+
934
+ const navType = node.type || NAVIGATION_TYPES.DYNAMIC
935
+ if (navType === NAVIGATION_TYPES.DYNAMIC) {
936
+ return node.viewId ? `View #${node.viewId}` : ''
937
+ } else if (navType === NAVIGATION_TYPES.STATIC) {
938
+ return node.path || ''
939
+ } else if (navType === NAVIGATION_TYPES.EXTERNAL) {
940
+ return node.externalUrl || ''
941
+ }
942
+ return ''
943
+ }
944
+
945
+ const handleAddChild = (e) => {
946
+ e.stopPropagation()
947
+ actions.addNode(node.id)
948
+ }
949
+
950
+ return (
951
+ <Box
952
+ ref={setNodeRef}
953
+ style={style}
954
+ sx={{
955
+ mb: 0.5,
956
+ ...(isDragging && {
957
+ boxShadow: 3,
958
+ transform: 'rotate(2deg)'
959
+ })
960
+ }}
961
+ >
962
+ <Box
963
+ sx={{
964
+ display: 'flex',
965
+ alignItems: 'center',
966
+ position: 'relative',
967
+ '&::before': depth > 0 ? {
968
+ content: '""',
969
+ position: 'absolute',
970
+ left: depth * 24 - 12,
971
+ top: 0,
972
+ bottom: 0,
973
+ width: '1px',
974
+ bgcolor: 'divider',
975
+ opacity: 0.3
976
+ } : {}
977
+ }}
978
+ >
979
+ {/* Tree connector lines */}
980
+ {depth > 0 && (
981
+ <Box
982
+ sx={{
983
+ position: 'absolute',
984
+ left: depth * 24 - 12,
985
+ top: -8,
986
+ width: 12,
987
+ height: '50%',
988
+ borderLeft: '1px solid',
989
+ borderBottom: '1px solid',
990
+ borderColor: 'divider',
991
+ opacity: 0.3
992
+ }}
993
+ />
994
+ )}
995
+ <ListItemButton
996
+ selected={isSelected}
997
+ onClick={() => actions.selectNode(node.id)}
998
+ {...attributes}
999
+ sx={{
1000
+ pl: depth * 3 + 2,
1001
+ flex: 1,
1002
+ borderRadius: 1,
1003
+ '&.Mui-selected': {
1004
+ backgroundColor: 'primary.main',
1005
+ color: 'primary.contrastText',
1006
+ '&:hover': {
1007
+ backgroundColor: 'primary.dark'
1008
+ },
1009
+ '& .MuiListItemIcon-root': {
1010
+ color: 'primary.contrastText'
1011
+ },
1012
+ '& .MuiListItemText-primary': {
1013
+ color: 'primary.contrastText'
1014
+ },
1015
+ '& .MuiListItemText-secondary': {
1016
+ color: 'primary.contrastText',
1017
+ opacity: 0.8
1018
+ }
1019
+ },
1020
+ }}
1021
+ >
1022
+ <ListItemIcon sx={{ minWidth: 32, display: 'flex', alignItems: 'center', gap: 0.5 }}>
1023
+ {/* Drag handle - only this element has drag listeners */}
1024
+ <Box
1025
+ {...(listeners || {})}
1026
+ sx={{
1027
+ display: 'inline-flex',
1028
+ alignItems: 'center',
1029
+ cursor: isDragging ? 'grabbing' : 'grab',
1030
+ p: 0.5,
1031
+ borderRadius: 1,
1032
+ '&:hover': {
1033
+ bgcolor: isSelected ? 'rgba(255,255,255,0.1)' : 'action.hover'
1034
+ }
1035
+ }}
1036
+ >
1037
+ {DragIcon && <DragIcon fontSize="small" sx={{ color: isSelected ? 'primary.contrastText' : 'text.secondary' }} />}
1038
+ </Box>
1039
+ {/* Always show chevron if children array exists (even if empty) */}
1040
+ {canHaveChildren && KeyboardArrowDownIcon && ChevronRightIcon && (
1041
+ <IconButton
1042
+ size="small"
1043
+ onClick={handleToggleExpand}
1044
+ onMouseDown={(e) => {
1045
+ e.preventDefault()
1046
+ e.stopPropagation()
1047
+ }}
1048
+ onPointerDown={(e) => {
1049
+ e.preventDefault()
1050
+ e.stopPropagation()
1051
+ }}
1052
+ onTouchStart={(e) => {
1053
+ e.stopPropagation()
1054
+ }}
1055
+ sx={{
1056
+ p: 0.5,
1057
+ color: isSelected ? 'primary.contrastText' : 'text.secondary',
1058
+ '&:hover': {
1059
+ bgcolor: isSelected ? 'rgba(255,255,255,0.1)' : 'action.hover'
1060
+ },
1061
+ pointerEvents: 'auto'
1062
+ }}
1063
+ >
1064
+ {isExpanded ? (
1065
+ <KeyboardArrowDownIcon fontSize="small" />
1066
+ ) : (
1067
+ <ChevronRightIcon fontSize="small" />
1068
+ )}
1069
+ </IconButton>
1070
+ )}
1071
+ {getNodeIcon() || <Box sx={{ width: 24, height: 24 }} />}
1072
+ </ListItemIcon>
1073
+ <ListItemText
1074
+ primary={node.title || 'Untitled'}
1075
+ secondary={getPathDisplay()}
1076
+ />
1077
+ <Box sx={{ display: 'flex', gap: 0.5 }} onClick={(e) => e.stopPropagation()}>
1078
+ <Tooltip title="Add Item Inside">
1079
+ <IconButton
1080
+ size="small"
1081
+ color="primary"
1082
+ onClick={handleAddChild}
1083
+ >
1084
+ <AddIcon fontSize="small" />
1085
+ </IconButton>
1086
+ </Tooltip>
1087
+ <Tooltip title="Clone">
1088
+ <IconButton
1089
+ size="small"
1090
+ onClick={(e) => {
1091
+ e.stopPropagation()
1092
+ actions.cloneNode(node.id)
1093
+ }}
1094
+ >
1095
+ <CloneIcon fontSize="small" />
1096
+ </IconButton>
1097
+ </Tooltip>
1098
+ {/* Don't show delete button for mainHome root */}
1099
+ {node.id !== 'mainHome-root' && (
1100
+ <Tooltip title="Delete">
1101
+ <IconButton
1102
+ size="small"
1103
+ color="error"
1104
+ onClick={(e) => {
1105
+ e.stopPropagation()
1106
+ if (confirm('Delete this item?')) {
1107
+ actions.deleteNode(node.id)
1108
+ }
1109
+ }}
1110
+ >
1111
+ <DeleteIcon fontSize="small" />
1112
+ </IconButton>
1113
+ </Tooltip>
1114
+ )}
1115
+ </Box>
1116
+ </ListItemButton>
1117
+ </Box>
1118
+ </Box>
1119
+ )
1120
+ }
1121
+
1122
+ /**
1123
+ * Tree Children Component - SortableContext wrapper for children
1124
+ * @dnd-kit handles nested structures natively
1125
+ */
1126
+ const TreeChildren = ({ parent, depth = 0 }) => {
1127
+ const { state } = useNavigator()
1128
+ const isExpanded = state.expandedNodes.includes(parent.id)
1129
+
1130
+ // Ensure children is always an array
1131
+ const children = Array.isArray(parent.children) ? parent.children : []
1132
+ const hasChildren = children.length > 0
1133
+
1134
+ // Always render if expanded (even if no children yet) to allow adding items
1135
+ if (!isExpanded) {
1136
+ return null
1137
+ }
1138
+
1139
+ const childIds = hasChildren
1140
+ ? children
1141
+ .filter(child => child && child.id)
1142
+ .map(child => String(child.id))
1143
+ : []
1144
+
1145
+ return (
1146
+ <SortableContext items={childIds} strategy={verticalListSortingStrategy}>
1147
+ <Box
1148
+ sx={{
1149
+ ml: 3
1150
+ }}
1151
+ >
1152
+ <Collapse in={isExpanded} timeout="auto" unmountOnExit>
1153
+ <List component="div" disablePadding>
1154
+ {hasChildren ? (
1155
+ children
1156
+ .filter(child => child && child.id)
1157
+ .map((child) => (
1158
+ <React.Fragment key={String(child.id)}>
1159
+ <TreeNode node={child} depth={depth + 1} />
1160
+ {state.expandedNodes.includes(child.id) && (
1161
+ <TreeChildren parent={child} depth={depth + 1} />
1162
+ )}
1163
+
1164
+ </React.Fragment>
1165
+ ))
1166
+ ) : (
1167
+ <Box sx={{ p: 2, textAlign: 'center', color: 'text.secondary' }}>
1168
+ <Typography variant="body2">No items yet. Click "Add Item Inside" to add.</Typography>
1169
+ </Box>
1170
+ )}
1171
+ </List>
1172
+ </Collapse>
1173
+ </Box>
1174
+ </SortableContext>
1175
+ )
1176
+ }
1177
+
1178
+ /**
1179
+ * Center Panel - Tree View with Root and Drag & Drop
1180
+ */
1181
+ const TreePanel = () => {
1182
+ const { state, actions } = useNavigator()
1183
+ const [activeId, setActiveId] = useState(null)
1184
+
1185
+ // @dnd-kit sensors - configure to prevent accidental drags when clicking buttons
1186
+ const sensors = useSensors(
1187
+ useSensor(PointerSensor, {
1188
+ activationConstraint: {
1189
+ distance: 8, // Require 8px movement before drag starts
1190
+ delay: 0,
1191
+ tolerance: 5
1192
+ }
1193
+ }),
1194
+ useSensor(KeyboardSensor, {
1195
+ coordinateGetter: sortableKeyboardCoordinates
1196
+ })
1197
+ )
1198
+
1199
+ const handleAddToRoot = () => {
1200
+ // Find mainHome root and add to it
1201
+ const mainHomeRoot = state.navConfig.items.find(item =>
1202
+ item.id === 'mainHome-root' || item.title === 'mainHome'
1203
+ )
1204
+
1205
+ if (mainHomeRoot) {
1206
+ // Add to mainHome root
1207
+ actions.addNode(mainHomeRoot.id)
1208
+ // Auto-expand mainHome if not already expanded
1209
+ if (!state.expandedNodes.includes('mainHome-root')) {
1210
+ actions.toggleExpand('mainHome-root')
1211
+ }
1212
+ } else {
1213
+ // No mainHome root, add to root (will be wrapped in mainHome on save)
1214
+ actions.addNode(null)
1215
+ }
1216
+ }
1217
+
1218
+ const handleDragStart = (event) => {
1219
+ setActiveId(event.active.id)
1220
+ }
1221
+
1222
+ const handleDragEnd = (event) => {
1223
+ const { active, over } = event
1224
+ setActiveId(null)
1225
+
1226
+ if (!over || active.id === over.id) {
1227
+ return
1228
+ }
1229
+
1230
+ // Helper to find node and its parent info
1231
+ const findNodeInfo = (items, targetId, parentId = null) => {
1232
+ for (let i = 0; i < items.length; i++) {
1233
+ const node = items[i]
1234
+ if (String(node.id) === String(targetId)) {
1235
+ return {
1236
+ node,
1237
+ parentId,
1238
+ index: i
1239
+ }
1240
+ }
1241
+ if (Array.isArray(node.children) && node.children.length > 0) {
1242
+ const found = findNodeInfo(node.children, targetId, node.id)
1243
+ if (found) return found
1244
+ }
1245
+ }
1246
+ return null
1247
+ }
1248
+
1249
+ const activeInfo = findNodeInfo(state.navConfig.items, active.id)
1250
+ const overInfo = findNodeInfo(state.navConfig.items, over.id)
1251
+
1252
+ if (!activeInfo || !overInfo) return
1253
+
1254
+ // Convert to the format expected by reorderNodes
1255
+ const source = {
1256
+ droppableId: activeInfo.parentId || 'root',
1257
+ index: activeInfo.index
1258
+ }
1259
+
1260
+ const destination = {
1261
+ droppableId: overInfo.parentId || 'root',
1262
+ index: overInfo.index
1263
+ }
1264
+
1265
+ actions.reorderNodes(source, destination)
1266
+ }
1267
+
1268
+ return (
1269
+ <Paper sx={{ p: 2, height: '100%', overflow: 'auto' }}>
1270
+ <Typography variant="h6" gutterBottom>
1271
+ Navigation Tree
1272
+ </Typography>
1273
+
1274
+ {/* Root Node */}
1275
+ <Paper
1276
+ variant="outlined"
1277
+ sx={{
1278
+ p: 2,
1279
+ mb: 2,
1280
+ bgcolor: 'background.default',
1281
+ border: '2px dashed',
1282
+ borderColor: 'divider'
1283
+ }}
1284
+ >
1285
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
1286
+ <Typography variant="subtitle1" fontWeight="bold">
1287
+ Root Navigation
1288
+ </Typography>
1289
+ <Button
1290
+ variant="contained"
1291
+ size="small"
1292
+ startIcon={<AddIcon />}
1293
+ onClick={handleAddToRoot}
1294
+ >
1295
+ Add Navigation Item
1296
+ </Button>
1297
+ </Box>
1298
+ <Typography variant="body2" color="text.secondary">
1299
+ Drag items to reorder. Items with children become groups automatically.
1300
+ </Typography>
1301
+ </Paper>
1302
+
1303
+ {/* Navigation Items with Drag and Drop */}
1304
+ <DndContext
1305
+ sensors={sensors}
1306
+ collisionDetection={closestCenter}
1307
+ onDragStart={handleDragStart}
1308
+ onDragEnd={handleDragEnd}
1309
+ >
1310
+ {(() => {
1311
+ // Find mainHome root
1312
+ const mainHomeRoot = state.navConfig.items.find(item =>
1313
+ item.id === 'mainHome-root' || item.title === 'mainHome'
1314
+ )
1315
+
1316
+ // If mainHome exists, use its children; otherwise use items directly
1317
+ const itemsToShow = mainHomeRoot && Array.isArray(mainHomeRoot.children)
1318
+ ? mainHomeRoot.children
1319
+ : state.navConfig.items
1320
+
1321
+ // Auto-expand mainHome root if it exists
1322
+ const shouldExpandMainHome = mainHomeRoot && !state.expandedNodes.includes('mainHome-root')
1323
+ if (shouldExpandMainHome) {
1324
+ actions.toggleExpand('mainHome-root')
1325
+ }
1326
+
1327
+ if (itemsToShow.length === 0 && !mainHomeRoot) {
1328
+ return <Alert severity="info">No navigation items yet. Click "Add Navigation Item" above to get started.</Alert>
1329
+ }
1330
+
1331
+ return (
1332
+ <SortableContext
1333
+ items={itemsToShow.map(node => String(node.id))}
1334
+ strategy={verticalListSortingStrategy}
1335
+ >
1336
+ <List>
1337
+ {/* Show mainHome root as header if it exists */}
1338
+ {mainHomeRoot && (
1339
+ <ListItem
1340
+ sx={{
1341
+ px: 2,
1342
+ py: 1.5,
1343
+ bgcolor: 'action.hover',
1344
+ borderBottom: '1px solid',
1345
+ borderColor: 'divider'
1346
+ }}
1347
+ >
1348
+ <ListItemIcon>
1349
+ <GroupIcon color="primary" />
1350
+ </ListItemIcon>
1351
+ <ListItemText
1352
+ primary={
1353
+ <Typography variant="subtitle1" fontWeight="bold">
1354
+ {mainHomeRoot.title || 'mainHome'}
1355
+ </Typography>
1356
+ }
1357
+ secondary="Root Navigation Group"
1358
+ />
1359
+ <IconButton
1360
+ size="small"
1361
+ onClick={() => actions.toggleExpand('mainHome-root')}
1362
+ >
1363
+ {state.expandedNodes.includes('mainHome-root') ? (
1364
+ <KeyboardArrowDownIcon />
1365
+ ) : (
1366
+ <ChevronRightIcon />
1367
+ )}
1368
+ </IconButton>
1369
+ </ListItem>
1370
+ )}
1371
+
1372
+ {/* Show children of mainHome */}
1373
+ {mainHomeRoot && state.expandedNodes.includes('mainHome-root') && (
1374
+ <TreeChildren parent={mainHomeRoot} depth={0} />
1375
+ )}
1376
+
1377
+ {/* If no mainHome root, show items directly */}
1378
+ {!mainHomeRoot && itemsToShow.map((node) => (
1379
+ <React.Fragment key={String(node.id)}>
1380
+ <TreeNode node={node} depth={0} />
1381
+ {state.expandedNodes.includes(node.id) && (
1382
+ <TreeChildren parent={node} depth={0} />
1383
+ )}
1384
+ </React.Fragment>
1385
+ ))}
1386
+ </List>
1387
+ </SortableContext>
1388
+ )
1389
+ })()}
1390
+ <DragOverlay>
1391
+ {activeId ? (() => {
1392
+ // Find the node being dragged
1393
+ const findNodeById = (items, id) => {
1394
+ for (const node of items) {
1395
+ if (String(node.id) === String(id)) return node
1396
+ if (Array.isArray(node.children)) {
1397
+ const found = findNodeById(node.children, id)
1398
+ if (found) return found
1399
+ }
1400
+ }
1401
+ return null
1402
+ }
1403
+ const draggedNode = findNodeById(state.navConfig.items, activeId)
1404
+ if (!draggedNode) return null
1405
+
1406
+ return (
1407
+ <Box
1408
+ sx={{
1409
+ p: 1.5,
1410
+ bgcolor: 'background.paper',
1411
+ boxShadow: 6,
1412
+ borderRadius: 1,
1413
+ border: '2px solid',
1414
+ borderColor: 'primary.main',
1415
+ minWidth: 200,
1416
+ opacity: 0.9
1417
+ }}
1418
+ >
1419
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
1420
+ <DragIcon sx={{ color: 'primary.main' }} />
1421
+ <Typography variant="body2" fontWeight="medium">
1422
+ {draggedNode.title || 'Untitled'}
1423
+ </Typography>
1424
+ </Box>
1425
+ </Box>
1426
+ )
1427
+ })() : null}
1428
+ </DragOverlay>
1429
+ </DndContext>
1430
+ </Paper>
1431
+ )
1432
+ }
1433
+
1434
+ /**
1435
+ * Left Panel - Properties
1436
+ */
1437
+ const PropertiesPanel = () => {
1438
+ const { state, actions } = useNavigator()
1439
+ const selectedNode = state.selectedNodeId
1440
+ ? findNodeInTree(state.navConfig.items, state.selectedNodeId)
1441
+ : null
1442
+
1443
+ if (!selectedNode) {
1444
+ return (
1445
+ <Paper sx={{ p: 2, height: '100%' }}>
1446
+ <Typography variant="h6" gutterBottom>
1447
+ Properties
1448
+ </Typography>
1449
+ <Alert severity="info">Select a node to edit its properties</Alert>
1450
+ </Paper>
1451
+ )
1452
+ }
1453
+
1454
+ const handleUpdate = (field, value) => {
1455
+ actions.updateNode(selectedNode.id, { [field]: value })
1456
+ }
1457
+
1458
+
1459
+ return (
1460
+ <Paper sx={{ p: 2, height: '100%', overflow: 'auto' }}>
1461
+ <Typography variant="h6" gutterBottom>
1462
+ Properties
1463
+ </Typography>
1464
+
1465
+ <Stack spacing={2}>
1466
+ <TextField
1467
+ label="Title"
1468
+ value={selectedNode.title || ''}
1469
+ onChange={(e) => handleUpdate('title', e.target.value)}
1470
+ fullWidth
1471
+ required
1472
+ />
1473
+
1474
+ {/* Navigation Type - show for both items and groups */}
1475
+ <FormControl fullWidth>
1476
+ <InputLabel>Navigation Type</InputLabel>
1477
+ <Select
1478
+ value={selectedNode.type || NAVIGATION_TYPES.DYNAMIC}
1479
+ label="Navigation Type"
1480
+ onChange={(e) => {
1481
+ const newType = e.target.value
1482
+ // Clear fields when switching types
1483
+ const updates = { type: newType }
1484
+ if (newType === NAVIGATION_TYPES.DYNAMIC) {
1485
+ updates.externalUrl = null
1486
+ } else if (newType === NAVIGATION_TYPES.STATIC) {
1487
+ updates.viewId = null
1488
+ updates.externalUrl = null
1489
+ } else if (newType === NAVIGATION_TYPES.EXTERNAL) {
1490
+ updates.viewId = null
1491
+ updates.path = null
1492
+ }
1493
+ actions.updateNode(selectedNode.id, updates)
1494
+ }}
1495
+ >
1496
+ <MenuItem value={NAVIGATION_TYPES.DYNAMIC}>Dynamic (View ID)</MenuItem>
1497
+ <MenuItem value={NAVIGATION_TYPES.STATIC}>Static (Path)</MenuItem>
1498
+ <MenuItem value={NAVIGATION_TYPES.EXTERNAL}>External (URL)</MenuItem>
1499
+ </Select>
1500
+ </FormControl>
1501
+
1502
+ {/* Dynamic type fields - available for both items and groups */}
1503
+ {selectedNode.type === NAVIGATION_TYPES.DYNAMIC && (
1504
+ <>
1505
+ <TextField
1506
+ label="View ID"
1507
+ type="number"
1508
+ value={selectedNode.viewId ?? ''}
1509
+ onChange={(e) => {
1510
+ const v = e.target.value.trim()
1511
+ actions.updateNode(selectedNode.id, { viewId: v === '' ? null : Number(v) })
1512
+ }}
1513
+ fullWidth
1514
+ placeholder="e.g., 22"
1515
+ helperText={(() => {
1516
+ if (!selectedNode.viewId) return null
1517
+ const view = state.registry[selectedNode.viewId]
1518
+ return view ? `View: ${view.name} (/${view.slug})` : 'View details not found'
1519
+ })()}
1520
+ />
1521
+ {/* Groups with viewId can also have a path */}
1522
+ {(selectedNode.children && selectedNode.children.length > 0) && selectedNode.viewId && (
1523
+ <TextField
1524
+ label="Path (optional for groups)"
1525
+ value={selectedNode.path || ''}
1526
+ onChange={(e) => {
1527
+ actions.updateNode(selectedNode.id, { path: e.target.value.trim() || null })
1528
+ }}
1529
+ fullWidth
1530
+ placeholder="e.g., /viewer/22"
1531
+ helperText="Groups with viewId can have a path"
1532
+ />
1533
+ )}
1534
+ </>
1535
+ )}
1536
+
1537
+ {/* Static type fields - only for items without children */}
1538
+ {selectedNode.type === NAVIGATION_TYPES.STATIC && (!selectedNode.children || selectedNode.children.length === 0) && (
1539
+ <TextField
1540
+ label="Path"
1541
+ value={selectedNode.path || ''}
1542
+ onChange={(e) => {
1543
+ actions.updateNode(selectedNode.id, { path: e.target.value.trim() || null })
1544
+ }}
1545
+ fullWidth
1546
+ placeholder="e.g., /mainHome or /users"
1547
+ />
1548
+ )}
1549
+
1550
+ {/* External type fields - only for items without children */}
1551
+ {selectedNode.type === NAVIGATION_TYPES.EXTERNAL && (!selectedNode.children || selectedNode.children.length === 0) && (
1552
+ <TextField
1553
+ label="External URL"
1554
+ value={selectedNode.externalUrl || ''}
1555
+ onChange={(e) => {
1556
+ actions.updateNode(selectedNode.id, { externalUrl: e.target.value.trim() || null })
1557
+ }}
1558
+ fullWidth
1559
+ placeholder="e.g., https://example.com"
1560
+ />
1561
+ )}
1562
+
1563
+
1564
+ <TextField
1565
+ label="Icon (MUI icon name)"
1566
+ value={selectedNode.icon || ''}
1567
+ onChange={(e) => handleUpdate('icon', e.target.value)}
1568
+ fullWidth
1569
+ placeholder="e.g., HomeOutline"
1570
+ />
1571
+
1572
+ <Chip
1573
+ label={Array.isArray(selectedNode.children) && selectedNode.children.length > 0 ? 'Group' : 'Navigation Item'}
1574
+ color={Array.isArray(selectedNode.children) && selectedNode.children.length > 0 ? 'primary' : 'default'}
1575
+ variant="outlined"
1576
+ />
1577
+ {Array.isArray(selectedNode.children) && selectedNode.children.length > 0 && (
1578
+ <Chip
1579
+ label={`${selectedNode.children.length} child items`}
1580
+ color="secondary"
1581
+ variant="outlined"
1582
+ />
1583
+ )}
1584
+ </Stack>
1585
+ </Paper>
1586
+ )
1587
+ }
1588
+
1589
+ /**
1590
+ * Preview Navigation Item Component
1591
+ */
1592
+ const PreviewNavItem = ({ node, depth = 0, onItemClick }) => {
1593
+ const [expanded, setExpanded] = useState(false)
1594
+
1595
+ // Safety check: ensure node exists
1596
+ if (!node || !node.id) {
1597
+ return null
1598
+ }
1599
+
1600
+ // Single source of truth: use only children array
1601
+ const hasChildren = Array.isArray(node.children) && node.children.length > 0
1602
+
1603
+ const getIcon = () => {
1604
+ if (node.icon && Icons[node.icon]) {
1605
+ const IconComponent = Icons[node.icon]
1606
+ return <IconComponent />
1607
+ }
1608
+ if (hasChildren) {
1609
+ return <GroupIcon />
1610
+ }
1611
+ return <ItemIcon />
1612
+ }
1613
+
1614
+
1615
+ return (
1616
+ <Box>
1617
+ <ListItemButton
1618
+ onClick={() => {
1619
+ if (hasChildren) {
1620
+ setExpanded(!expanded)
1621
+ } else if (onItemClick) {
1622
+ onItemClick(node)
1623
+ }
1624
+ }}
1625
+ sx={{
1626
+ pl: depth * 2 + 2,
1627
+ py: 1.5,
1628
+ borderRadius: 1,
1629
+ mb: 0.5,
1630
+ '&:hover': {
1631
+ backgroundColor: 'action.hover'
1632
+ }
1633
+ }}
1634
+ >
1635
+ <ListItemIcon sx={{ minWidth: 40, color: 'text.secondary' }}>
1636
+ {getIcon()}
1637
+ </ListItemIcon>
1638
+ <ListItemText
1639
+ primary={node.title || 'Untitled'}
1640
+ secondary={
1641
+ hasChildren
1642
+ ? `${node.children.length} items`
1643
+ : (node.viewId ? `View #${node.viewId}` : '')
1644
+ }
1645
+ />
1646
+ {hasChildren && (
1647
+ <Box sx={{ ml: 1 }}>
1648
+ {expanded ? <KeyboardArrowDownIcon /> : <ChevronRightIcon />}
1649
+ </Box>
1650
+ )}
1651
+ </ListItemButton>
1652
+ {hasChildren && (
1653
+ <Collapse in={expanded} timeout="auto" unmountOnExit>
1654
+ <List component="div" disablePadding>
1655
+ {node.children
1656
+ .filter(child => child && child.id) // Filter out invalid children
1657
+ .map(child => (
1658
+ <PreviewNavItem
1659
+ key={child.id}
1660
+ node={child}
1661
+ depth={depth + 1}
1662
+ onItemClick={onItemClick}
1663
+ />
1664
+ ))}
1665
+ </List>
1666
+ </Collapse>
1667
+ )}
1668
+ </Box>
1669
+ )
1670
+ }
1671
+
1672
+ /**
1673
+ * Preview Panel - Shows how navigation will look
1674
+ */
1675
+ const PreviewPanel = () => {
1676
+ const { state, actions } = useNavigator()
1677
+
1678
+ const handleItemClick = (node) => {
1679
+ actions.selectNode(node.id)
1680
+ }
1681
+
1682
+ return (
1683
+ <Box sx={{ height: '100%', display: 'flex', flexDirection: 'column', bgcolor: 'background.default' }}>
1684
+ <Paper sx={{ p: 2, borderRadius: 0, borderBottom: 1, borderColor: 'divider' }}>
1685
+ <Typography variant="h6" gutterBottom>
1686
+ Navigation Preview
1687
+ </Typography>
1688
+ <Typography variant="body2" color="text.secondary">
1689
+ This is how your navigation will appear in the application
1690
+ </Typography>
1691
+ </Paper>
1692
+
1693
+ {state.navConfig.items.length === 0 ? (
1694
+ <Box sx={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
1695
+ <Alert severity="info" sx={{ maxWidth: 500 }}>
1696
+ No navigation items to preview. Add items from the builder.
1697
+ </Alert>
1698
+ </Box>
1699
+ ) : (
1700
+ <Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
1701
+ <Paper
1702
+ variant="outlined"
1703
+ sx={{
1704
+ p: 2,
1705
+ bgcolor: 'background.paper',
1706
+ borderRadius: 2,
1707
+ border: 1,
1708
+ borderColor: 'divider',
1709
+ maxWidth: 400,
1710
+ mx: 'auto'
1711
+ }}
1712
+ >
1713
+ <Typography variant="subtitle2" color="text.secondary" sx={{ mb: 2, px: 2 }}>
1714
+ {state.navConfig.name || 'Navigation Menu'}
1715
+ </Typography>
1716
+ <List sx={{ py: 0 }}>
1717
+ {state.navConfig.items.map(node => (
1718
+ <PreviewNavItem
1719
+ key={node.id}
1720
+ node={node}
1721
+ onItemClick={handleItemClick}
1722
+ />
1723
+ ))}
1724
+ </List>
1725
+ </Paper>
1726
+
1727
+ {/* JSON Output Preview */}
1728
+ <Paper
1729
+ variant="outlined"
1730
+ sx={{
1731
+ mt: 3,
1732
+ p: 2,
1733
+ bgcolor: 'background.paper',
1734
+ borderRadius: 2,
1735
+ border: 1,
1736
+ borderColor: 'divider',
1737
+ maxWidth: 800,
1738
+ mx: 'auto'
1739
+ }}
1740
+ >
1741
+ <Typography variant="subtitle2" gutterBottom>
1742
+ JSON Output
1743
+ </Typography>
1744
+ <Box
1745
+ component="pre"
1746
+ sx={{
1747
+ p: 2,
1748
+ direction:'rtl',
1749
+ bgcolor: 'grey.100',
1750
+ borderRadius: 1,
1751
+ overflow: 'auto',
1752
+ fontSize: '0.75rem',
1753
+ maxHeight: 300
1754
+ }}
1755
+ >
1756
+ {JSON.stringify(state.navConfig, null, 2)}
1757
+ </Box>
1758
+ </Paper>
1759
+ </Box>
1760
+ )}
1761
+ </Box>
1762
+ )
1763
+ }
1764
+
1765
+ /**
1766
+ * Main Navigator Builder Component
1767
+ */
1768
+ const NavigatorBuilder = () => {
1769
+ const { state, actions } = useNavigator()
1770
+ const [saveDialogOpen, setSaveDialogOpen] = useState(false)
1771
+ const [loadDialogOpen, setLoadDialogOpen] = useState(false)
1772
+ const [navName, setNavName] = useState(state.navConfig.name)
1773
+ const [viewMode, setViewMode] = useState('builder') // 'builder' or 'preview'
1774
+
1775
+ const fetchNavigationSettings = useCallback(async () => {
1776
+ try {
1777
+ const result = await GetService(Endpoints.Navigator.Get.NavigatorSetting, false)
1778
+ console.log('Raw fetched result:', result)
1779
+
1780
+ let data = result
1781
+ if (result && result.data) {
1782
+ try {
1783
+ data = JSON.parse(result.data)
1784
+ } catch (e) {
1785
+ console.error('Error parsing result.value:', e)
1786
+ }
1787
+ }
1788
+
1789
+ if (data && data.config) {
1790
+ console.log('Loading navigation config from DTO:', data.config)
1791
+ actions.loadNavigation(data)
1792
+ setNavName(data.config.name)
1793
+ } else if (data && data.items) {
1794
+ // Fallback for direct config object
1795
+ actions.loadNavigation({ config: data, registry: {} })
1796
+ setNavName(data.name || 'Loaded Navigation')
1797
+ }
1798
+ } catch (error) {
1799
+ console.error('Error fetching navigation settings:', error)
1800
+ }
1801
+ }, [actions])
1802
+
1803
+ useEffect(() => {
1804
+ fetchNavigationSettings()
1805
+ }, [])
1806
+
1807
+ const handleSave = async () => {
1808
+ try {
1809
+ const config = actions.getNavConfig()
1810
+ config.name = navName
1811
+
1812
+ // Extract all viewIds and their titles from the navigation config
1813
+ const viewIdToTitle = new Map()
1814
+ const collectViewIds = (items) => {
1815
+ items.forEach(item => {
1816
+ if (item.viewId) {
1817
+ viewIdToTitle.set(String(item.viewId), item.title || `View ${item.viewId}`)
1818
+ }
1819
+ if (item.children && Array.isArray(item.children)) {
1820
+ collectViewIds(item.children)
1821
+ }
1822
+ })
1823
+ }
1824
+ collectViewIds(config.items)
1825
+
1826
+ // Helper function to create slug from title
1827
+ const createSlug = (title) => {
1828
+ return title
1829
+ .toLowerCase()
1830
+ .trim()
1831
+ .replace(/[^\w\s-]/g, '') // Remove special characters
1832
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
1833
+ .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
1834
+ }
1835
+
1836
+ // Build registry directly from viewIds with nav titles
1837
+ const latestRegistry = {}
1838
+
1839
+ viewIdToTitle.forEach((title, viewId) => {
1840
+ latestRegistry[viewId] = {
1841
+ id: Number(viewId),
1842
+ name: title,
1843
+ slug: createSlug(title)
1844
+ }
1845
+ })
1846
+
1847
+ const saveData = {
1848
+ config: config,
1849
+ registry: latestRegistry
1850
+ }
1851
+
1852
+ const jsonString = JSON.stringify(saveData)
1853
+
1854
+ // Use 'navigatorSetting' key as requested in previous sessions for DataTypes.Params
1855
+ const result = await PostService(Endpoints.Navigator.Post.NavigatorSetting, true, {
1856
+ value: jsonString
1857
+ })
1858
+
1859
+ if (result) {
1860
+ setSaveDialogOpen(false)
1861
+ }
1862
+ } catch (error) {
1863
+ console.error('Error saving navigation:', error)
1864
+ }
1865
+ }
1866
+
1867
+ const handleLoad = (event) => {
1868
+ const file = event.target.files[0]
1869
+ if (!file) {
1870
+ console.warn('No file selected')
1871
+ return
1872
+ }
1873
+
1874
+ console.log('Loading file:', file.name, file.type, file.size)
1875
+
1876
+ const reader = new FileReader()
1877
+ reader.onload = (e) => {
1878
+ try {
1879
+ const rawData = e.target.result
1880
+ console.log('File content length:', rawData.length)
1881
+
1882
+ const data = JSON.parse(rawData)
1883
+ console.log('Parsed JSON data:', data)
1884
+
1885
+ // Handle different JSON formats
1886
+ let config
1887
+ if (Array.isArray(data)) {
1888
+ // If it's an array, wrap it in a config object
1889
+ console.log('Detected array format, wrapping in config object')
1890
+ config = {
1891
+ name: 'Loaded Navigation',
1892
+ items: data,
1893
+ createdAt: new Date().toISOString(),
1894
+ updatedAt: new Date().toISOString()
1895
+ }
1896
+ } else if (data.items && Array.isArray(data.items)) {
1897
+ // If it's already a config object with items
1898
+ console.log('Detected config object format')
1899
+ config = {
1900
+ name: data.name || 'Loaded Navigation',
1901
+ items: data.items,
1902
+ createdAt: data.createdAt || new Date().toISOString(),
1903
+ updatedAt: data.updatedAt || new Date().toISOString()
1904
+ }
1905
+ } else {
1906
+ throw new Error('Invalid navigation format. Expected array or object with items array.')
1907
+ }
1908
+
1909
+ console.log('Final config to load:', config)
1910
+ actions.loadNavigation(config)
1911
+ setNavName(config.name)
1912
+
1913
+ // Show success message
1914
+ alert(`Navigation loaded successfully! Found ${config.items.length} root item(s).`)
1915
+
1916
+ // Reset file input so same file can be loaded again
1917
+ event.target.value = ''
1918
+ } catch (error) {
1919
+ console.error('Error loading navigation:', error)
1920
+ console.error('Error stack:', error.stack)
1921
+ alert(`Error loading navigation: ${error.message || 'Invalid JSON file'}\n\nCheck console for details.`)
1922
+ }
1923
+ }
1924
+ reader.onerror = (error) => {
1925
+ console.error('FileReader error:', error)
1926
+ alert('Error reading file. Please try again.')
1927
+ }
1928
+ reader.readAsText(file)
1929
+ }
1930
+
1931
+ return (
1932
+ <Box sx={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
1933
+ {/* Header */}
1934
+ <Paper sx={{ p: 2, borderRadius: 0 }}>
1935
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
1936
+ <Typography variant="h5">Navigator Builder</Typography>
1937
+ <Box sx={{ display: 'flex', gap: 1 }}>
1938
+ <Button
1939
+ startIcon={<ValidIcon />}
1940
+ onClick={() => actions.validate()}
1941
+ color={state.validation.isValid ? 'success' : 'warning'}
1942
+ >
1943
+ Validate
1944
+ </Button>
1945
+ <Button
1946
+ startIcon={<LoadIcon />}
1947
+ onClick={fetchNavigationSettings}
1948
+ variant="outlined"
1949
+ >
1950
+ Reload
1951
+ </Button>
1952
+ <Button
1953
+ startIcon={<LoadIcon />}
1954
+ onClick={() => document.getElementById('load-input').click()}
1955
+ variant="outlined"
1956
+ >
1957
+ Load File
1958
+ </Button>
1959
+ <input
1960
+ id="load-input"
1961
+ type="file"
1962
+ accept=".json"
1963
+ style={{ display: 'none' }}
1964
+ onChange={handleLoad}
1965
+ />
1966
+ <Button
1967
+ startIcon={<SaveIcon />}
1968
+ onClick={() => setSaveDialogOpen(true)}
1969
+ variant="contained"
1970
+ disabled={!state.validation.isValid}
1971
+ >
1972
+ Save
1973
+ </Button>
1974
+ </Box>
1975
+ </Box>
1976
+
1977
+ {/* Validation Status */}
1978
+ {state.validation.errors.length > 0 && (
1979
+ <Alert severity="error" sx={{ mt: 1 }}>
1980
+ {state.validation.errors.length} error(s) found
1981
+ </Alert>
1982
+ )}
1983
+ {state.validation.warnings.length > 0 && (
1984
+ <Alert severity="warning" sx={{ mt: 1 }}>
1985
+ {state.validation.warnings.length} warning(s) found
1986
+ </Alert>
1987
+ )}
1988
+ {state.validation.isValid && state.validation.errors.length === 0 && (
1989
+ <Alert severity="success" sx={{ mt: 1 }}>
1990
+ Navigation is valid
1991
+ </Alert>
1992
+ )}
1993
+ </Paper>
1994
+
1995
+ {/* View Mode Toggle */}
1996
+ <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
1997
+ <Tabs
1998
+ value={viewMode}
1999
+ onChange={(e, newValue) => setViewMode(newValue)}
2000
+ sx={{ minHeight: 48 }}
2001
+ >
2002
+ <Tab
2003
+ label="Builder"
2004
+ value="builder"
2005
+ icon={<EditIcon />}
2006
+ iconPosition="start"
2007
+ />
2008
+ <Tab
2009
+ label="Preview"
2010
+ value="preview"
2011
+ icon={<PreviewIcon />}
2012
+ iconPosition="start"
2013
+ />
2014
+ </Tabs>
2015
+ </Box>
2016
+
2017
+ {/* Main Content */}
2018
+ {viewMode === 'builder' ? (
2019
+ <Box sx={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
2020
+ {/* Left Panel - Properties */}
2021
+ <Box sx={{ width: 300, borderRight: 1, borderColor: 'divider' }}>
2022
+ <PropertiesPanel />
2023
+ </Box>
2024
+
2025
+ {/* Center Panel - Tree */}
2026
+ <Box sx={{ flex: 1 }}>
2027
+ <TreePanel />
2028
+ </Box>
2029
+ </Box>
2030
+ ) : (
2031
+ <Box sx={{ flex: 1, overflow: 'hidden' }}>
2032
+ <PreviewPanel />
2033
+ </Box>
2034
+ )}
2035
+
2036
+ {/* Save Dialog */}
2037
+ <Dialog open={saveDialogOpen} onClose={() => setSaveDialogOpen(false)}>
2038
+ <DialogTitle>Save Navigation</DialogTitle>
2039
+ <DialogContent>
2040
+ <TextField
2041
+ label="Navigation Name"
2042
+ value={navName}
2043
+ onChange={(e) => setNavName(e.target.value)}
2044
+ fullWidth
2045
+ sx={{ mt: 1 }}
2046
+ />
2047
+ </DialogContent>
2048
+ <DialogActions>
2049
+ <Button onClick={() => setSaveDialogOpen(false)}>Cancel</Button>
2050
+ <Button onClick={handleSave} variant="contained">
2051
+ Save
2052
+ </Button>
2053
+ </DialogActions>
2054
+ </Dialog>
2055
+ </Box>
2056
+ )
2057
+ }
2058
+
2059
+ // ============================================================================
2060
+ // EXPORT
2061
+ // ============================================================================
2062
+
2063
+ export default function NavigatorBuilderPage() {
2064
+ return (
2065
+ <NavigatorProvider>
2066
+ <NavigatorBuilder />
2067
+ </NavigatorProvider>
2068
+ )
2069
+ }
2070
+