@goplusvn/core 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/package.json +2 -1
  2. package/src/assets/erp_wallpaper.png +0 -0
  3. package/src/assets/goeat_logo.png +0 -0
  4. package/src/audit/audit-manager.ts +139 -0
  5. package/src/audit/index.ts +11 -0
  6. package/src/audit/memory-audit-logger.ts +86 -0
  7. package/src/audit/types.ts +50 -0
  8. package/src/auth/auth-service.ts +97 -0
  9. package/src/auth/index.ts +266 -0
  10. package/src/code-generation/index.ts +69 -0
  11. package/src/configs/auth-routes.ts +17 -0
  12. package/src/configs/crud.ts +136 -0
  13. package/src/configs/data/navigations.ts +781 -0
  14. package/src/configs/data/oauth-links.ts +10 -0
  15. package/src/configs/entities/material-categories.config.ts +125 -0
  16. package/src/configs/i18n.ts +12 -0
  17. package/src/configs/index.ts +26 -0
  18. package/src/configs/status.ts +25 -0
  19. package/src/configs/themes.ts +100 -0
  20. package/src/crud/components/crud-bulk-actions.tsx +91 -0
  21. package/src/crud/components/crud-card-view.tsx +241 -0
  22. package/src/crud/components/crud-context.tsx +122 -0
  23. package/src/crud/components/crud-delete-dialog.tsx +145 -0
  24. package/src/crud/components/crud-dialog.tsx +406 -0
  25. package/src/crud/components/crud-empty-state.tsx +104 -0
  26. package/src/crud/components/crud-export-button.tsx +170 -0
  27. package/src/crud/components/crud-field-renderer.tsx +653 -0
  28. package/src/crud/components/crud-filter-chips.tsx +102 -0
  29. package/src/crud/components/crud-filters/checkbox-filter.tsx +97 -0
  30. package/src/crud/components/crud-filters/datetime-filter.tsx +83 -0
  31. package/src/crud/components/crud-filters/filter-builder.tsx +66 -0
  32. package/src/crud/components/crud-filters/index.tsx +76 -0
  33. package/src/crud/components/crud-filters/radio-filter.tsx +86 -0
  34. package/src/crud/components/crud-filters/select-filter.tsx +141 -0
  35. package/src/crud/components/crud-filters/text-filter.tsx +86 -0
  36. package/src/crud/components/crud-form.tsx +642 -0
  37. package/src/crud/components/crud-import-dialog.tsx +440 -0
  38. package/src/crud/components/crud-infinite-scroll.tsx +116 -0
  39. package/src/crud/components/crud-page.tsx +1017 -0
  40. package/src/crud/components/crud-provider.tsx +277 -0
  41. package/src/crud/components/crud-row-actions.tsx +189 -0
  42. package/src/crud/components/crud-search.tsx +82 -0
  43. package/src/crud/components/crud-sheet.tsx +336 -0
  44. package/src/crud/components/crud-table-skeleton.tsx +26 -0
  45. package/src/crud/components/crud-table-toolbar.tsx +91 -0
  46. package/src/crud/components/crud-table.tsx +352 -0
  47. package/src/crud/components/crud-virtual-table.tsx +55 -0
  48. package/src/crud/components/index.tsx +20 -0
  49. package/src/crud/crud-filters/checkbox-filter.tsx +87 -0
  50. package/src/crud/crud-filters/datetime-filter.tsx +82 -0
  51. package/src/crud/crud-filters/filter-builder.tsx +64 -0
  52. package/src/crud/crud-filters/index.tsx +78 -0
  53. package/src/crud/crud-filters/radio-filter.tsx +79 -0
  54. package/src/crud/crud-filters/select-filter.tsx +148 -0
  55. package/src/crud/crud-filters/text-filter.tsx +81 -0
  56. package/src/crud/index.ts +43 -0
  57. package/src/crud/lib/crud-service.test.ts +334 -0
  58. package/src/crud/lib/crud-service.ts +358 -0
  59. package/src/crud/lib/crud-utils.test.ts +354 -0
  60. package/src/crud/lib/crud-utils.ts +299 -0
  61. package/src/crud/lib/crud-validator.ts +247 -0
  62. package/src/crud/lib/data-loader.ts +234 -0
  63. package/src/crud/lib/field-calculator.ts +241 -0
  64. package/src/crud/lib/field-formatter.ts +240 -0
  65. package/src/crud/lib/import-export-service.test.ts +290 -0
  66. package/src/crud/lib/import-export-service.ts +352 -0
  67. package/src/crud/lib/import-server-utils.ts +109 -0
  68. package/src/crud/lib/lazy-loader.ts +241 -0
  69. package/src/crud/lib/parse-filters.ts +85 -0
  70. package/src/crud/lib/permissions.ts +52 -0
  71. package/src/crud/lib/serialize-config.ts +60 -0
  72. package/src/crud/lib/stream-loader.ts +145 -0
  73. package/src/crud/lib/translate-config.ts +335 -0
  74. package/src/crud/lib/types.ts +11 -0
  75. package/src/crud/pages/entity-crud-page.tsx +144 -0
  76. package/src/crud/server.ts +8 -0
  77. package/src/home/constants.tsx +142 -0
  78. package/src/home/feature-showcase.tsx +171 -0
  79. package/src/home/home-page.tsx +191 -0
  80. package/src/home/hooks/index.ts +1 -0
  81. package/src/home/hooks/useWidgetPreferences.ts +167 -0
  82. package/src/home/index.ts +33 -0
  83. package/src/home/quick-access-dialog.tsx +271 -0
  84. package/src/home/quick-access-menu.tsx +267 -0
  85. package/src/home/types.ts +140 -0
  86. package/src/home/welcome-card.tsx +92 -0
  87. package/src/home/widget-container.tsx +258 -0
  88. package/src/home/widgets/base-widget.tsx +200 -0
  89. package/src/home/widgets/customers-widget.tsx +74 -0
  90. package/src/home/widgets/index.ts +6 -0
  91. package/src/home/widgets/orders-widget.tsx +87 -0
  92. package/src/home/widgets/revenue-widget.tsx +71 -0
  93. package/src/home/widgets/stock-widget.tsx +109 -0
  94. package/src/hooks/index.tsx +598 -0
  95. package/src/hooks/use-tenant.test.tsx +30 -0
  96. package/src/hooks/use-tenant.ts +5 -0
  97. package/src/index.ts +17 -0
  98. package/src/infrastructure/__tests__/architecture-verification.spec.ts +103 -0
  99. package/src/infrastructure/api-service.ts +317 -0
  100. package/src/infrastructure/cache/cache-manager.ts +107 -0
  101. package/src/infrastructure/cache/cache.ts +120 -0
  102. package/src/infrastructure/cache/index.ts +8 -0
  103. package/src/infrastructure/cache/types.ts +48 -0
  104. package/src/infrastructure/cron/cron-manager.ts +239 -0
  105. package/src/infrastructure/cron/index.ts +6 -0
  106. package/src/infrastructure/cron/types.ts +41 -0
  107. package/src/infrastructure/event-bus/event-bus.ts +145 -0
  108. package/src/infrastructure/event-bus/index.ts +2 -0
  109. package/src/infrastructure/event-bus/types.ts +22 -0
  110. package/src/infrastructure/index.ts +32 -0
  111. package/src/infrastructure/lock/decorators.ts +67 -0
  112. package/src/infrastructure/lock/index.ts +2 -0
  113. package/src/infrastructure/lock/lock-manager.ts +33 -0
  114. package/src/infrastructure/logger/index.ts +2 -0
  115. package/src/infrastructure/logger/logger.ts +96 -0
  116. package/src/infrastructure/logger/types.ts +25 -0
  117. package/src/layout/index.tsx +185 -0
  118. package/src/navigation/index.ts +91 -0
  119. package/src/notification/index.ts +14 -0
  120. package/src/notification/notification-service.ts +120 -0
  121. package/src/notification/storage/in-memory.ts +56 -0
  122. package/src/notification/storage/index.ts +1 -0
  123. package/src/notification/types.ts +51 -0
  124. package/src/organization/branch-service.ts +299 -0
  125. package/src/organization/branches.config.ts +154 -0
  126. package/src/organization/index.ts +5 -0
  127. package/src/plugin/apps-registry.ts +97 -0
  128. package/src/plugin/index.ts +5 -0
  129. package/src/plugin/types.ts +41 -0
  130. package/src/providers/index.tsx +109 -0
  131. package/src/providers/tenant-provider.tsx +45 -0
  132. package/src/rbac/components/roles/role-card.tsx +158 -0
  133. package/src/rbac/components/roles/role-stats-cards.tsx +29 -0
  134. package/src/rbac/components/roles/role-toolbar.tsx +123 -0
  135. package/src/rbac/hooks/use-role-operations.ts +159 -0
  136. package/src/rbac/hooks/use-roles-data.ts +59 -0
  137. package/src/rbac/index.ts +297 -0
  138. package/src/rbac/lib/permission-helpers.ts +63 -0
  139. package/src/rbac/pages/action-list-page.tsx +25 -0
  140. package/src/rbac/pages/resource-list-page.tsx +25 -0
  141. package/src/rbac/pages/role-list-page.tsx +378 -0
  142. package/src/rbac/permission-service.ts +140 -0
  143. package/src/rbac/permissions.ts +135 -0
  144. package/src/rbac/resource-service.ts +115 -0
  145. package/src/rbac/resource-validator.ts +119 -0
  146. package/src/rbac/role-service.ts +165 -0
  147. package/src/rbac/server.ts +16 -0
  148. package/src/rbac/types.ts +38 -0
  149. package/src/schemas/action.schema.ts +66 -0
  150. package/src/schemas/branch.schema.ts +52 -0
  151. package/src/schemas/coming-soon-schema.ts +9 -0
  152. package/src/schemas/company.schema.ts +44 -0
  153. package/src/schemas/forgot-passward-schema.ts +9 -0
  154. package/src/schemas/index.ts +30 -0
  155. package/src/schemas/material-category.schema.ts +43 -0
  156. package/src/schemas/material-pricing.schema.ts +74 -0
  157. package/src/schemas/material.schema.ts +76 -0
  158. package/src/schemas/materials.ts +52 -0
  159. package/src/schemas/new-passward-schema.ts +15 -0
  160. package/src/schemas/partner-company.schema.ts +149 -0
  161. package/src/schemas/register-schema.ts +36 -0
  162. package/src/schemas/resource.schema.ts +133 -0
  163. package/src/schemas/role.schema.ts +11 -0
  164. package/src/schemas/sign-in-schema.ts +24 -0
  165. package/src/schemas/supplier-pricing.schema.ts +15 -0
  166. package/src/schemas/supplier.schema.ts +120 -0
  167. package/src/schemas/system-category-group.schema.ts +67 -0
  168. package/src/schemas/system-category.schema.ts +77 -0
  169. package/src/schemas/system-config.schema.ts +118 -0
  170. package/src/schemas/uom.schema.ts +75 -0
  171. package/src/schemas/user-supplier.schema.ts +179 -0
  172. package/src/schemas/user.schema.ts +18 -0
  173. package/src/schemas/verify-email-schema.ts +9 -0
  174. package/src/schemas/warehouse.schema.ts +49 -0
  175. package/src/system/components/categories/category-list.tsx +529 -0
  176. package/src/system/components/categories/category-manager.tsx +89 -0
  177. package/src/system/components/categories/group-sidebar.tsx +308 -0
  178. package/src/system/components/settings/setting-dialogs.tsx +197 -0
  179. package/src/system/components/settings/setting-field.tsx +291 -0
  180. package/src/system/components/settings/setting-form-dialog.tsx +308 -0
  181. package/src/system/components/settings/settings-groups.ts +80 -0
  182. package/src/system/components/settings/settings-search.tsx +71 -0
  183. package/src/system/components/settings/settings-section.tsx +74 -0
  184. package/src/system/components/settings/settings-sidebar.tsx +81 -0
  185. package/src/system/constants.ts +3 -0
  186. package/src/system/index.ts +150 -0
  187. package/src/system/job-manager.ts +176 -0
  188. package/src/system/pages/components/categories/category-list.tsx +537 -0
  189. package/src/system/pages/components/categories/category-manager.tsx +90 -0
  190. package/src/system/pages/components/categories/group-sidebar.tsx +311 -0
  191. package/src/system/pages/components/settings/sales-rules-settings.tsx +222 -0
  192. package/src/system/pages/components/settings/setting-dialogs.tsx +197 -0
  193. package/src/system/pages/components/settings/setting-field.tsx +292 -0
  194. package/src/system/pages/components/settings/setting-form-dialog.tsx +308 -0
  195. package/src/system/pages/components/settings/settings-groups.ts +87 -0
  196. package/src/system/pages/components/settings/settings-page.tsx +372 -0
  197. package/src/system/pages/components/settings/settings-search.tsx +71 -0
  198. package/src/system/pages/components/settings/settings-section.tsx +74 -0
  199. package/src/system/pages/components/settings/settings-sidebar.tsx +81 -0
  200. package/src/system/pages/components/settings/system-settings.tsx +244 -0
  201. package/src/system/pages/system-category-page.tsx +15 -0
  202. package/src/system/pages/system-settings-page.tsx +380 -0
  203. package/src/system/schemas/system-category-group.schema.ts +46 -0
  204. package/src/system/schemas/system-category.schema.ts +56 -0
  205. package/src/system/services/settings-service.ts +127 -0
  206. package/src/system/services/system-category-service.ts +63 -0
  207. package/src/system/types.ts +45 -0
  208. package/src/types/index.ts +703 -0
  209. package/src/ui/auth/auth-layout.tsx +135 -0
  210. package/src/ui/auth/forgot-password-form.tsx +98 -0
  211. package/src/ui/auth/index.tsx +7 -0
  212. package/src/ui/auth/new-password-form.tsx +107 -0
  213. package/src/ui/auth/oauth-links.tsx +30 -0
  214. package/src/ui/auth/register-form.tsx +202 -0
  215. package/src/ui/auth/sign-in-form.tsx +238 -0
  216. package/src/ui/auth/verify-email-form.tsx +104 -0
  217. package/src/ui/crud/index.tsx +10 -0
  218. package/src/ui/data-display/accordion.tsx +65 -0
  219. package/src/ui/data-display/aspect-ratio.tsx +11 -0
  220. package/src/ui/data-display/avatar.tsx +163 -0
  221. package/src/ui/data-display/bento-grid.tsx +77 -0
  222. package/src/ui/data-display/carousel.tsx +249 -0
  223. package/src/ui/data-display/chart.tsx +363 -0
  224. package/src/ui/data-display/code-block-highlight.tsx +54 -0
  225. package/src/ui/data-display/collapsible.tsx +42 -0
  226. package/src/ui/data-display/compact-stat-bar.tsx +149 -0
  227. package/src/ui/data-display/data-table/data-table-context.tsx +255 -0
  228. package/src/ui/data-display/data-table/data-table-empty-state.tsx +133 -0
  229. package/src/ui/data-display/data-table/data-table-skeleton.tsx +145 -0
  230. package/src/ui/data-display/data-table/data-table-toolbar.tsx +353 -0
  231. package/src/ui/data-display/data-table/data-table.tsx +597 -0
  232. package/src/ui/data-display/data-table/index.ts +44 -0
  233. package/src/ui/data-display/data-table-column-header.tsx +75 -0
  234. package/src/ui/data-display/data-table-pagination.tsx +130 -0
  235. package/src/ui/data-display/data-table-view-options.tsx +59 -0
  236. package/src/ui/data-display/formatted-number-input.tsx +210 -0
  237. package/src/ui/data-display/highlight.tsx +20 -0
  238. package/src/ui/data-display/hover-card.tsx +48 -0
  239. package/src/ui/data-display/index.tsx +50 -0
  240. package/src/ui/data-display/iphone-15-pro.tsx +114 -0
  241. package/src/ui/data-display/kanban/index.ts +4 -0
  242. package/src/ui/data-display/kanban/kanban-board.tsx +192 -0
  243. package/src/ui/data-display/kanban/kanban-column.tsx +74 -0
  244. package/src/ui/data-display/kanban/kanban-item.tsx +50 -0
  245. package/src/ui/data-display/kanban/kanban-types.ts +21 -0
  246. package/src/ui/data-display/kpi-card.tsx +68 -0
  247. package/src/ui/data-display/media-grid.tsx +110 -0
  248. package/src/ui/data-display/safari.tsx +175 -0
  249. package/src/ui/data-display/show-more-text.tsx +55 -0
  250. package/src/ui/data-display/tabs.tsx +68 -0
  251. package/src/ui/data-display/timeline.tsx +256 -0
  252. package/src/ui/feedback/alert.tsx +60 -0
  253. package/src/ui/feedback/context-menu.tsx +245 -0
  254. package/src/ui/feedback/drawer.tsx +132 -0
  255. package/src/ui/feedback/error-dialog.tsx +273 -0
  256. package/src/ui/feedback/index.tsx +183 -0
  257. package/src/ui/feedback/progress.tsx +32 -0
  258. package/src/ui/feedback/sheet.tsx +148 -0
  259. package/src/ui/feedback/sonner.tsx +36 -0
  260. package/src/ui/forms/command.tsx +157 -0
  261. package/src/ui/forms/date-picker.tsx +73 -0
  262. package/src/ui/forms/date-range-picker.tsx +76 -0
  263. package/src/ui/forms/date-time-picker.tsx +109 -0
  264. package/src/ui/forms/editor/editor-menu-bar.tsx +394 -0
  265. package/src/ui/forms/editor/index.tsx +130 -0
  266. package/src/ui/forms/editor/multi-select-example.tsx +1234 -0
  267. package/src/ui/forms/emoji-picker.tsx +109 -0
  268. package/src/ui/forms/file-dropzone.tsx +169 -0
  269. package/src/ui/forms/file-thumbnail.tsx +29 -0
  270. package/src/ui/forms/index.tsx +201 -0
  271. package/src/ui/forms/input-file.tsx +99 -0
  272. package/src/ui/forms/input-group.tsx +46 -0
  273. package/src/ui/forms/input-otp.tsx +81 -0
  274. package/src/ui/forms/input-phone.tsx +172 -0
  275. package/src/ui/forms/input-spin.tsx +116 -0
  276. package/src/ui/forms/input-tags.tsx +219 -0
  277. package/src/ui/forms/input-time.tsx +42 -0
  278. package/src/ui/forms/multi-select.tsx +629 -0
  279. package/src/ui/forms/multiple-date-picker.tsx +74 -0
  280. package/src/ui/forms/radio-group.tsx +42 -0
  281. package/src/ui/forms/rating.tsx +158 -0
  282. package/src/ui/forms/time-picker.tsx +57 -0
  283. package/src/ui/index.tsx +17 -0
  284. package/src/ui/layout/animated-list.tsx +77 -0
  285. package/src/ui/layout/animated-sidebar.tsx +294 -0
  286. package/src/ui/layout/command-menu.tsx +355 -0
  287. package/src/ui/layout/customizer.tsx +324 -0
  288. package/src/ui/layout/footer.tsx +43 -0
  289. package/src/ui/layout/full-screen-toggle.tsx +52 -0
  290. package/src/ui/layout/header-breadcrumb.tsx +77 -0
  291. package/src/ui/layout/horizontal-layout-header.tsx +83 -0
  292. package/src/ui/layout/horizontal-layout.tsx +50 -0
  293. package/src/ui/layout/index.tsx +25 -0
  294. package/src/ui/layout/language-dropdown.tsx +103 -0
  295. package/src/ui/layout/logo.tsx +63 -0
  296. package/src/ui/layout/main-layout.tsx +57 -0
  297. package/src/ui/layout/mode-dropdown.tsx +58 -0
  298. package/src/ui/layout/notification-dropdown.tsx +127 -0
  299. package/src/ui/layout/page-tabs.tsx +306 -0
  300. package/src/ui/layout/route-cache.tsx +214 -0
  301. package/src/ui/layout/sidebar-group-icon-menu.tsx +195 -0
  302. package/src/ui/layout/sidebar.tsx +279 -0
  303. package/src/ui/layout/tab-content-cache.tsx +201 -0
  304. package/src/ui/layout/tab-navigation-provider.tsx +536 -0
  305. package/src/ui/layout/toggle-mobile-sidebar.tsx +33 -0
  306. package/src/ui/layout/top-bar-header-menubar.tsx +412 -0
  307. package/src/ui/layout/user-dropdown.tsx +188 -0
  308. package/src/ui/layout/vertical-layout-header.tsx +65 -0
  309. package/src/ui/layout/vertical-layout.tsx +47 -0
  310. package/src/ui/management/audit-log-page.tsx +209 -0
  311. package/src/ui/management/cache-management.tsx +349 -0
  312. package/src/ui/management/index.ts +3 -0
  313. package/src/ui/management/job-management.tsx +308 -0
  314. package/src/ui/pages/not-found.tsx +30 -0
  315. package/src/ui/primitives/badge.tsx +66 -0
  316. package/src/ui/primitives/breadcrumb.tsx +103 -0
  317. package/src/ui/primitives/button.tsx +129 -0
  318. package/src/ui/primitives/calendar.tsx +74 -0
  319. package/src/ui/primitives/card.tsx +86 -0
  320. package/src/ui/primitives/checkbox.tsx +31 -0
  321. package/src/ui/primitives/client.ts +30 -0
  322. package/src/ui/primitives/combobox.tsx +290 -0
  323. package/src/ui/primitives/dialog.tsx +121 -0
  324. package/src/ui/primitives/dropdown-menu.tsx +239 -0
  325. package/src/ui/primitives/dynamic-icon.tsx +24 -0
  326. package/src/ui/primitives/index.tsx +134 -0
  327. package/src/ui/primitives/input-number.tsx +131 -0
  328. package/src/ui/primitives/input.tsx +22 -0
  329. package/src/ui/primitives/keyboard.tsx +23 -0
  330. package/src/ui/primitives/label.tsx +24 -0
  331. package/src/ui/primitives/menubar.tsx +262 -0
  332. package/src/ui/primitives/navigation-menu.tsx +157 -0
  333. package/src/ui/primitives/pagination.tsx +118 -0
  334. package/src/ui/primitives/popover.tsx +56 -0
  335. package/src/ui/primitives/prefetch-link.tsx +60 -0
  336. package/src/ui/primitives/resizable.tsx +59 -0
  337. package/src/ui/primitives/scroll-area.tsx +63 -0
  338. package/src/ui/primitives/select.tsx +172 -0
  339. package/src/ui/primitives/separator.tsx +51 -0
  340. package/src/ui/primitives/sidebar.tsx +844 -0
  341. package/src/ui/primitives/slider.tsx +27 -0
  342. package/src/ui/primitives/status-badge.tsx +47 -0
  343. package/src/ui/primitives/sticky-layout.tsx +50 -0
  344. package/src/ui/primitives/switch.tsx +29 -0
  345. package/src/ui/primitives/table.tsx +116 -0
  346. package/src/ui/primitives/tabs.tsx +55 -0
  347. package/src/ui/primitives/toggle-group.tsx +70 -0
  348. package/src/ui/primitives/toggle.tsx +47 -0
  349. package/src/ui/primitives/tooltip.tsx +59 -0
  350. package/src/user/components/dangerous-zone.tsx +34 -0
  351. package/src/user/components/delete-account-form.tsx +40 -0
  352. package/src/user/components/index.ts +4 -0
  353. package/src/user/components/profile-info-form.tsx +390 -0
  354. package/src/user/components/profile-info.tsx +32 -0
  355. package/src/user/components/unified-profile-dialog.tsx +1019 -0
  356. package/src/user/components/user-stats.tsx +27 -0
  357. package/src/user/components/user-toolbar.tsx +137 -0
  358. package/src/user/components/users-card-view.tsx +253 -0
  359. package/src/user/index.ts +11 -0
  360. package/src/user/pages/user-list-page.tsx +234 -0
  361. package/src/user/pages/users-client-page.tsx +385 -0
  362. package/src/user/profile-page.tsx +19 -0
  363. package/src/user/schemas.ts +68 -0
  364. package/src/user/types.ts +34 -0
  365. package/src/user/user-service.ts +538 -0
  366. package/src/utils/index.ts +906 -0
  367. package/src/workflow/activity-timeline.tsx +412 -0
  368. package/src/workflow/approval-workflow.tsx +31 -0
  369. package/src/workflow/index.ts +2 -0
@@ -0,0 +1,195 @@
1
+ "use client";
2
+
3
+ import { useMemo } from "react";
4
+ import Link from "next/link";
5
+
6
+ import type { DictionaryType } from "../../hooks";
7
+ import type {
8
+ LocaleType,
9
+ NavigationNestedItem,
10
+ NavigationRootItem,
11
+ } from "../../types";
12
+
13
+ import { ensureLocalizedPathname } from "../../utils";
14
+ import {
15
+ cn,
16
+ getDictionaryValue,
17
+ isActivePathname,
18
+ titleCaseToCamelCase,
19
+ } from "../../utils";
20
+
21
+ import {
22
+ Badge,
23
+ DynamicIcon,
24
+ Popover,
25
+ PopoverContent,
26
+ PopoverTrigger,
27
+ PrefetchLink,
28
+ ScrollArea,
29
+ SidebarMenuButton,
30
+ } from "../index";
31
+
32
+ interface SidebarGroupIconMenuProps {
33
+ groupTitle: string;
34
+ items: NavigationRootItem[];
35
+ dictionary: DictionaryType;
36
+ locale: LocaleType;
37
+ pathname: string;
38
+ isRTL: boolean;
39
+ onItemClick?: () => void;
40
+ }
41
+
42
+ export function SidebarGroupIconMenu({
43
+ groupTitle,
44
+ items,
45
+ dictionary,
46
+ locale,
47
+ pathname,
48
+ isRTL,
49
+ onItemClick,
50
+ }: SidebarGroupIconMenuProps) {
51
+ // Get first item with icon for trigger button
52
+ const firstItemWithIcon = useMemo(
53
+ () => items.find((item) => "iconName" in item && item.iconName),
54
+ [items],
55
+ );
56
+
57
+ const renderMenuItem = (
58
+ item: NavigationRootItem | NavigationNestedItem,
59
+ level: number = 0,
60
+ ) => {
61
+ const title = getDictionaryValue(
62
+ // @ts-ignore - dictionary typing might need adjustment or ignore
63
+ titleCaseToCamelCase(item.title),
64
+ dictionary.navigation,
65
+ item.title,
66
+ );
67
+ const label =
68
+ item.label &&
69
+ getDictionaryValue(titleCaseToCamelCase(item.label), dictionary.label);
70
+
71
+ // Handle nested items
72
+ if (item.items && item.items.length > 0) {
73
+ return (
74
+ <div key={item.title} className="space-y-1">
75
+ <div
76
+ className={cn(
77
+ "flex items-center gap-2 rounded-md px-2 py-1.5 text-sm text-sidebar-foreground/70",
78
+ level > 0 && "text-xs",
79
+ )}
80
+ >
81
+ {"iconName" in item && (
82
+ <DynamicIcon name={item.iconName} className="h-4 w-4 shrink-0" />
83
+ )}
84
+ <span className="font-medium">{title}</span>
85
+ {label && (
86
+ <Badge variant="secondary" className="ml-auto text-xs">
87
+ {label}
88
+ </Badge>
89
+ )}
90
+ </div>
91
+ <div className={cn("ml-4 space-y-1", level > 0 && "ml-2")}>
92
+ {item.items.map((subItem) => renderMenuItem(subItem, level + 1))}
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ // Handle regular link items
99
+ if ("href" in item && item.href) {
100
+ const localizedPathname = ensureLocalizedPathname(item.href, locale);
101
+ const isActive = isActivePathname(localizedPathname, pathname);
102
+ const isCrudLink = localizedPathname.startsWith("/crud/");
103
+ const crudEntity = isCrudLink
104
+ ? localizedPathname.replace("/crud/", "").split("/")[0]
105
+ : undefined;
106
+
107
+ // Need to find where PrefetchLink is defined.
108
+ // Assuming it is exported from "../index".
109
+
110
+ const LinkComponent: any = isCrudLink ? PrefetchLink : Link;
111
+
112
+ return (
113
+ <LinkComponent
114
+ key={item.title}
115
+ href={localizedPathname}
116
+ {...(isCrudLink && crudEntity ? { crudEntity } : {})}
117
+ className={cn(
118
+ "group/menu-item relative flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors",
119
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
120
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
121
+ isActive &&
122
+ "bg-sidebar-accent font-medium text-sidebar-accent-foreground",
123
+ level > 0 && "text-xs pl-6",
124
+ )}
125
+ onClick={onItemClick}
126
+ >
127
+ {"iconName" in item && (
128
+ <DynamicIcon
129
+ name={item.iconName}
130
+ className="h-4 w-4 shrink-0 text-sidebar-foreground/70 group-hover/menu-item:text-sidebar-accent-foreground"
131
+ />
132
+ )}
133
+ <span className="flex-1 truncate">{title}</span>
134
+ {label && (
135
+ <Badge variant="secondary" className="ml-auto shrink-0 text-xs">
136
+ {label}
137
+ </Badge>
138
+ )}
139
+ {isActive && (
140
+ <div className="absolute left-0 top-1/2 h-1.5 w-1 -translate-y-1/2 rounded-r-full bg-sidebar-accent-foreground" />
141
+ )}
142
+ </LinkComponent>
143
+ );
144
+ }
145
+
146
+ return null;
147
+ };
148
+
149
+ if (!firstItemWithIcon || !("iconName" in firstItemWithIcon)) {
150
+ return null;
151
+ }
152
+
153
+ return (
154
+ <Popover>
155
+ <PopoverTrigger asChild>
156
+ <SidebarMenuButton
157
+ className="size-8! p-2! justify-center!"
158
+ tooltip={groupTitle}
159
+ >
160
+ <DynamicIcon name={firstItemWithIcon.iconName} className="h-4 w-4" />
161
+ </SidebarMenuButton>
162
+ </PopoverTrigger>
163
+ <PopoverContent
164
+ side={isRTL ? "left" : "right"}
165
+ align="start"
166
+ sideOffset={12}
167
+ className="w-64 p-0"
168
+ >
169
+ <div className="flex flex-col">
170
+ {/* Header */}
171
+ <div className="border-b px-3 py-2.5">
172
+ <div className="flex items-center gap-2">
173
+ {"iconName" in firstItemWithIcon && (
174
+ <DynamicIcon
175
+ name={firstItemWithIcon.iconName}
176
+ className="h-4 w-4 text-sidebar-foreground/70"
177
+ />
178
+ )}
179
+ <span className="text-sm font-semibold text-sidebar-foreground">
180
+ {groupTitle}
181
+ </span>
182
+ </div>
183
+ </div>
184
+
185
+ {/* Menu Items */}
186
+ <ScrollArea className="max-h-[calc(100vh-12rem)]">
187
+ <div className="p-2 space-y-1">
188
+ {items.map((item) => renderMenuItem(item))}
189
+ </div>
190
+ </ScrollArea>
191
+ </div>
192
+ </PopoverContent>
193
+ </Popover>
194
+ );
195
+ }
@@ -0,0 +1,279 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useParams, usePathname } from "next/navigation";
5
+ import { ChevronDown } from "lucide-react";
6
+
7
+ import type { DictionaryType } from "../../hooks"; // Assuming generic type location
8
+ import type {
9
+ LocaleType,
10
+ NavigationNestedItem,
11
+ NavigationRootItem,
12
+ NavigationType,
13
+ } from "../../types";
14
+
15
+ import { i18n } from "../../configs";
16
+ import { ensureLocalizedPathname } from "../../utils";
17
+ import {
18
+ cn,
19
+ getDictionaryValue,
20
+ isActivePathname,
21
+ titleCaseToCamelCase,
22
+ } from "../../utils";
23
+
24
+ import { useSettings } from "../../hooks"; // Exported from core/hooks
25
+
26
+ import {
27
+ Badge,
28
+ Collapsible,
29
+ CollapsibleContent,
30
+ CollapsibleTrigger,
31
+ DynamicIcon,
32
+ PrefetchLink,
33
+ ScrollArea,
34
+ Sidebar as SidebarWrapper,
35
+ SidebarContent,
36
+ SidebarGroup,
37
+ SidebarGroupContent,
38
+ SidebarGroupLabel,
39
+ SidebarHeader,
40
+ SidebarMenu,
41
+ SidebarMenuButton,
42
+ SidebarMenuSubButton, // Added this import
43
+ SidebarMenuItem,
44
+ SidebarMenuSub,
45
+ useSidebar,
46
+ } from "../index"; // Importing from index
47
+
48
+ import { CommandMenu } from "./command-menu";
49
+ import { Logo } from "./logo";
50
+ import { SidebarGroupIconMenu } from "./sidebar-group-icon-menu";
51
+
52
+ interface SidebarProps {
53
+ dictionary: DictionaryType;
54
+ navigation: NavigationType[];
55
+ onGlobalSearch?: (query: string) => void;
56
+ searchResults?: any[]; // SearchGroup[] - using any to avoid import issues for now
57
+ searchLoading?: boolean;
58
+ }
59
+
60
+ export function AppSidebar({
61
+ dictionary,
62
+ navigation,
63
+ onGlobalSearch,
64
+ searchResults,
65
+ searchLoading,
66
+ }: SidebarProps) {
67
+ // Navigation is now REQUIRED prop, no fallback to internal data
68
+ const navData = navigation;
69
+ const pathname = usePathname();
70
+ const params = useParams();
71
+ const { openMobile, setOpenMobile, isMobile, state, isHoverExpanded } =
72
+ useSidebar();
73
+ const { settings } = useSettings();
74
+
75
+ const locale = params.lang as LocaleType;
76
+ const direction = i18n.localeDirection[locale];
77
+ const isRTL = (direction as "ltr" | "rtl") === "rtl";
78
+ const isHoizontalAndDesktop = settings.layout === "horizontal" && !isMobile;
79
+
80
+ // If the layout is horizontal and not on mobile, don't render the sidebar. (We use a menubar for horizontal layout navigation.)
81
+ if (isHoizontalAndDesktop) return null;
82
+
83
+ const renderMenuItem = (
84
+ item: NavigationRootItem | NavigationNestedItem,
85
+ isSub = false,
86
+ ) => {
87
+ const title = getDictionaryValue(
88
+ titleCaseToCamelCase(item.title),
89
+ dictionary.navigation,
90
+ item.title, // Fallback to original title if not found
91
+ );
92
+ const label =
93
+ item.label &&
94
+ getDictionaryValue(titleCaseToCamelCase(item.label), dictionary.label);
95
+
96
+ // If the item has nested items, render it with a collapsible dropdown.
97
+ if (item.items) {
98
+ return (
99
+ <Collapsible className="group/collapsible" defaultOpen={true}>
100
+ <CollapsibleTrigger asChild>
101
+ <SidebarMenuButton className="w-full justify-between [&[data-state=open]>svg]:rotate-180">
102
+ <span className="flex items-center">
103
+ {"iconName" in item && (
104
+ <DynamicIcon name={item.iconName} className="me-2 h-4 w-4" />
105
+ )}
106
+ <span>{title}</span>
107
+ {"label" in item && (
108
+ <Badge variant="secondary" className="me-2">
109
+ {label}
110
+ </Badge>
111
+ )}
112
+ </span>
113
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
114
+ </SidebarMenuButton>
115
+ </CollapsibleTrigger>
116
+ <CollapsibleContent className="overflow-hidden data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
117
+ <SidebarMenuSub>
118
+ {item.items.map((subItem: NavigationNestedItem) => (
119
+ <SidebarMenuItem key={subItem.title}>
120
+ {renderMenuItem(subItem, true)}
121
+ </SidebarMenuItem>
122
+ ))}
123
+ </SidebarMenuSub>
124
+ </CollapsibleContent>
125
+ </Collapsible>
126
+ );
127
+ }
128
+
129
+ // Otherwise, render the item with a link.
130
+ if ("href" in item) {
131
+ const localizedPathname = ensureLocalizedPathname(item.href, locale);
132
+ const isExactMatchRequired =
133
+ item.title === "Tổng quan" ||
134
+ item.title === "Overview" ||
135
+ item.title === "Dashboard";
136
+ const isActive = isActivePathname(
137
+ localizedPathname,
138
+ pathname,
139
+ isExactMatchRequired,
140
+ );
141
+
142
+ // ✅ Check if this is a CRUD link for prefetching
143
+ const isCrudLink = localizedPathname.startsWith("/crud/");
144
+ const crudEntity = isCrudLink
145
+ ? localizedPathname.replace("/crud/", "").split("/")[0]
146
+ : undefined;
147
+ const LinkComponent = isCrudLink ? PrefetchLink : Link;
148
+
149
+ const MenuButtonComponent = isSub
150
+ ? SidebarMenuSubButton
151
+ : SidebarMenuButton;
152
+
153
+ return (
154
+ <MenuButtonComponent
155
+ isActive={isActive}
156
+ onClick={() => setOpenMobile(!openMobile)}
157
+ asChild
158
+ >
159
+ <LinkComponent
160
+ href={localizedPathname}
161
+ {...(isCrudLink && crudEntity ? { crudEntity } : {})}
162
+ >
163
+ {"iconName" in item && (
164
+ <DynamicIcon name={item.iconName} className="h-4 w-4" />
165
+ )}
166
+ <span>{title}</span>
167
+ {"label" in item && <Badge variant="secondary">{label}</Badge>}
168
+ </LinkComponent>
169
+ </MenuButtonComponent>
170
+ );
171
+ }
172
+ };
173
+
174
+ // Enforce icon mode for Carbon style consistency if not set explicitly to offcanvas by some override (though we prefer icon)
175
+ const collapsibleMode =
176
+ settings.sidebarCollapsible === "offcanvas"
177
+ ? "icon"
178
+ : settings.sidebarCollapsible || "icon";
179
+
180
+ return (
181
+ <SidebarWrapper
182
+ side={isRTL ? "right" : "left"}
183
+ variant={settings.sidebarVariant}
184
+ collapsible={collapsibleMode}
185
+ >
186
+ <SidebarHeader className="space-y-2 group-data-[collapsible=icon]:items-center group-data-[hover-expanded=true]:!items-stretch">
187
+ <Link
188
+ href={ensureLocalizedPathname("/", locale)}
189
+ className={cn(
190
+ "w-fit pb-0",
191
+ state === "collapsed" && !isHoverExpanded && !isMobile
192
+ ? "p-1"
193
+ : "p-2",
194
+ )}
195
+ onClick={() => isMobile && setOpenMobile(!openMobile)}
196
+ >
197
+ <Logo
198
+ className="group-data-[collapsible=icon]:justify-center group-data-[hover-expanded=true]:!justify-start"
199
+ size={
200
+ state === "collapsed" && !isHoverExpanded && !isMobile
201
+ ? "sm"
202
+ : "md"
203
+ }
204
+ showText={state === "expanded" || isHoverExpanded || isMobile}
205
+ collapsed={state === "collapsed" && !isHoverExpanded && !isMobile}
206
+ />
207
+ </Link>
208
+ <CommandMenu
209
+ dictionary={dictionary}
210
+ navigation={navData}
211
+ variant={
212
+ state === "collapsed" && !isHoverExpanded && !isMobile
213
+ ? "icon"
214
+ : "default"
215
+ }
216
+ buttonClassName={cn(
217
+ "group-data-[collapsible=icon]:mx-auto",
218
+ "group-data-[hover-expanded=true]:!mx-0 group-data-[hover-expanded=true]:!w-full",
219
+ )}
220
+ onSearch={onGlobalSearch}
221
+ searchResults={searchResults}
222
+ loading={searchLoading}
223
+ />
224
+ </SidebarHeader>
225
+ <ScrollArea>
226
+ <SidebarContent className="gap-0">
227
+ {navData.map((nav) => {
228
+ const title = getDictionaryValue(
229
+ titleCaseToCamelCase(nav.title),
230
+ dictionary.navigation,
231
+ nav.title, // Fallback to original title if not found
232
+ );
233
+
234
+ // Only show icon mode if collapsed AND NOT hover expanded AND NOT mobile
235
+ const isIconMode =
236
+ collapsibleMode === "icon" &&
237
+ state === "collapsed" &&
238
+ !isHoverExpanded &&
239
+ !isMobile;
240
+
241
+ const groupContent = (
242
+ <SidebarGroup key={nav.title}>
243
+ <SidebarGroupLabel>{title}</SidebarGroupLabel>
244
+ <SidebarGroupContent>
245
+ <SidebarMenu>
246
+ {nav.items.map((item) => (
247
+ <SidebarMenuItem key={item.title}>
248
+ {renderMenuItem(item)}
249
+ </SidebarMenuItem>
250
+ ))}
251
+ </SidebarMenu>
252
+ </SidebarGroupContent>
253
+ </SidebarGroup>
254
+ );
255
+
256
+ // When in icon mode, show icon menu instead of full group
257
+ if (isIconMode) {
258
+ return (
259
+ <div key={nav.title} className="flex justify-center p-2">
260
+ <SidebarGroupIconMenu
261
+ groupTitle={title}
262
+ items={nav.items}
263
+ dictionary={dictionary}
264
+ locale={locale}
265
+ pathname={pathname}
266
+ isRTL={isRTL}
267
+ onItemClick={() => isMobile && setOpenMobile(false)}
268
+ />
269
+ </div>
270
+ );
271
+ }
272
+
273
+ return groupContent;
274
+ })}
275
+ </SidebarContent>
276
+ </ScrollArea>
277
+ </SidebarWrapper>
278
+ );
279
+ }
@@ -0,0 +1,201 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useState,
7
+ useCallback,
8
+ useRef,
9
+ useEffect,
10
+ } from "react";
11
+ import { usePathname } from "next/navigation";
12
+ import type { ReactNode } from "react";
13
+
14
+ interface CachedContent {
15
+ html: string;
16
+ scrollPosition: number;
17
+ timestamp: number;
18
+ data?: unknown;
19
+ }
20
+
21
+ interface TabContentCacheContextValue {
22
+ getCachedContent: (path: string) => CachedContent | null;
23
+ setCachedContent: (path: string, content: CachedContent) => void;
24
+ clearCache: (path?: string) => void;
25
+ clearAllCache: () => void;
26
+ isCached: (path: string) => boolean;
27
+ }
28
+
29
+ const TabContentCacheContext = createContext<
30
+ TabContentCacheContextValue | undefined
31
+ >(undefined);
32
+
33
+ const CACHE_STORAGE_KEY = "tab-content-cache";
34
+ const CACHE_EXPIRY = 30 * 60 * 1000; // 30 minutes
35
+
36
+ export function TabContentCacheProvider({ children }: { children: ReactNode }) {
37
+ const [cache, setCache] = useState<Map<string, CachedContent>>(new Map());
38
+ const pathname = usePathname();
39
+ const scrollPositionsRef = useRef<Map<string, number>>(new Map());
40
+
41
+ // Load cache from sessionStorage on mount
42
+ useEffect(() => {
43
+ if (typeof window === "undefined") return;
44
+
45
+ try {
46
+ const stored = sessionStorage.getItem(CACHE_STORAGE_KEY);
47
+ if (stored) {
48
+ const parsedCache = JSON.parse(stored) as Record<string, CachedContent>;
49
+ const cacheMap = new Map(Object.entries(parsedCache));
50
+
51
+ // Remove expired entries
52
+ const now = Date.now();
53
+ const validCache = new Map<string, CachedContent>();
54
+ for (const [path, content] of cacheMap.entries()) {
55
+ if (now - content.timestamp < CACHE_EXPIRY) {
56
+ validCache.set(path, content);
57
+ }
58
+ }
59
+
60
+ setCache(validCache);
61
+ }
62
+ } catch (error) {
63
+ console.error("Failed to load tab content cache:", error);
64
+ }
65
+ }, []);
66
+
67
+ // Save cache to sessionStorage whenever it changes
68
+ useEffect(() => {
69
+ if (typeof window === "undefined") return;
70
+
71
+ try {
72
+ const cacheObj = Object.fromEntries(cache);
73
+ sessionStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(cacheObj));
74
+ } catch (error) {
75
+ console.error("Failed to save tab content cache:", error);
76
+ }
77
+ }, [cache]);
78
+
79
+ // Save scroll position before navigation
80
+ useEffect(() => {
81
+ const handleScroll = () => {
82
+ if (pathname) {
83
+ scrollPositionsRef.current.set(pathname, window.scrollY);
84
+ }
85
+ };
86
+
87
+ window.addEventListener("scroll", handleScroll, { passive: true });
88
+ return () => window.removeEventListener("scroll", handleScroll);
89
+ }, [pathname]);
90
+
91
+ // Restore scroll position when pathname changes
92
+ useEffect(() => {
93
+ if (!pathname) return;
94
+
95
+ const cachedContent = cache.get(pathname);
96
+ if (cachedContent) {
97
+ // Restore scroll position after a short delay to ensure content is rendered
98
+ setTimeout(() => {
99
+ window.scrollTo({
100
+ top:
101
+ cachedContent.scrollPosition ||
102
+ scrollPositionsRef.current.get(pathname) ||
103
+ 0,
104
+ behavior: "instant",
105
+ });
106
+ }, 50);
107
+ }
108
+ }, [pathname, cache]);
109
+
110
+ const getCachedContent = useCallback(
111
+ (path: string): CachedContent | null => {
112
+ const cached = cache.get(path);
113
+ if (!cached) return null;
114
+
115
+ // Check if cache is expired
116
+ const now = Date.now();
117
+ if (now - cached.timestamp > CACHE_EXPIRY) {
118
+ cache.delete(path);
119
+ return null;
120
+ }
121
+
122
+ return cached;
123
+ },
124
+ [cache],
125
+ );
126
+
127
+ const setCachedContent = useCallback(
128
+ (path: string, content: CachedContent) => {
129
+ setCache((prev) => {
130
+ const newCache = new Map(prev);
131
+ newCache.set(path, {
132
+ ...content,
133
+ scrollPosition: scrollPositionsRef.current.get(path) || 0,
134
+ timestamp: Date.now(),
135
+ });
136
+ return newCache;
137
+ });
138
+ },
139
+ [],
140
+ );
141
+
142
+ const clearCache = useCallback((path?: string) => {
143
+ if (path) {
144
+ setCache((prev) => {
145
+ const newCache = new Map(prev);
146
+ newCache.delete(path);
147
+ return newCache;
148
+ });
149
+ scrollPositionsRef.current.delete(path);
150
+ } else {
151
+ setCache(new Map());
152
+ scrollPositionsRef.current.clear();
153
+ }
154
+ }, []);
155
+
156
+ const clearAllCache = useCallback(() => {
157
+ setCache(new Map());
158
+ scrollPositionsRef.current.clear();
159
+ if (typeof window !== "undefined") {
160
+ sessionStorage.removeItem(CACHE_STORAGE_KEY);
161
+ }
162
+ }, []);
163
+
164
+ const isCached = useCallback(
165
+ (path: string): boolean => {
166
+ const cached = cache.get(path);
167
+ if (!cached) return false;
168
+
169
+ const now = Date.now();
170
+ return now - cached.timestamp < CACHE_EXPIRY;
171
+ },
172
+ [cache],
173
+ );
174
+
175
+ return (
176
+ <TabContentCacheContext.Provider
177
+ value={{
178
+ getCachedContent,
179
+ setCachedContent,
180
+ clearCache,
181
+ clearAllCache,
182
+ isCached,
183
+ }}
184
+ >
185
+ {children}
186
+ </TabContentCacheContext.Provider>
187
+ );
188
+ }
189
+
190
+ const noopTabContentCache: TabContentCacheContextValue = {
191
+ getCachedContent: () => null,
192
+ setCachedContent: () => {},
193
+ clearCache: () => {},
194
+ clearAllCache: () => {},
195
+ isCached: () => false,
196
+ };
197
+
198
+ export function useTabContentCache() {
199
+ const context = useContext(TabContentCacheContext);
200
+ return context ?? noopTabContentCache;
201
+ }