@quazardous/qdadm 1.19.3

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 (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,438 @@
1
+ /**
2
+ * useEntityItemPage - Base composable for single entity item pages
3
+ *
4
+ * Provides common functionality for pages that display a single entity:
5
+ * - Entity loading by ID from route params
6
+ * - Loading/error state management
7
+ * - Active stack integration (auto-updates navigation context)
8
+ * - Manager access for child composables
9
+ * - **Parent chain auto-detection** from route.meta.parent
10
+ *
11
+ * Used as base for:
12
+ * - Show pages (read-only detail pages)
13
+ * - useEntityItemFormPage (create/edit forms)
14
+ */
15
+ import { ref, computed, onMounted, inject, provide, type Ref, type ComputedRef } from 'vue'
16
+ import { useRoute } from 'vue-router'
17
+ import { useStackHydrator, type StackHydratorReturn } from '../chain/useStackHydrator'
18
+ import type { EntityManagerCrud } from '../entity/EntityManager.interface'
19
+
20
+ /**
21
+ * Parent configuration from route.meta.parent
22
+ */
23
+ export interface ParentConfig {
24
+ entity: string
25
+ param: string
26
+ foreignKey?: string
27
+ parent?: ParentConfig
28
+ }
29
+
30
+ /**
31
+ * Field definition interface
32
+ */
33
+ export interface FieldDefinition {
34
+ name: string
35
+ type?: string
36
+ schemaType?: string
37
+ label?: string
38
+ required?: boolean
39
+ default?: unknown
40
+ options?: unknown[]
41
+ optionLabel?: string
42
+ optionValue?: string
43
+ placeholder?: string
44
+ disabled?: boolean
45
+ readonly?: boolean
46
+ editable?: boolean
47
+ reference?: string
48
+ validate?: (value: unknown, formData: unknown) => boolean | string | null | undefined
49
+ [key: string]: unknown
50
+ }
51
+
52
+ /**
53
+ * Context for create operation with parent chain
54
+ */
55
+ export interface CreateContext {
56
+ parentChain?: Array<{ entity: string; id: string | number }>
57
+ }
58
+
59
+ /**
60
+ * Entity manager interface for entity item pages (re-export for convenience)
61
+ */
62
+ export type EntityManager = EntityManagerCrud
63
+
64
+ /**
65
+ * Orchestrator interface
66
+ */
67
+ interface Orchestrator {
68
+ get: (entityName: string) => EntityManager
69
+ }
70
+
71
+ /**
72
+ * Options for useEntityItemPage
73
+ */
74
+ export interface UseEntityItemPageOptions<T = unknown> {
75
+ /** Entity name */
76
+ entity: string
77
+ /** Auto-load entity on mount (default: true) */
78
+ loadOnMount?: boolean
79
+ /** Auto-load parent entity from route.meta.parent (default: true) */
80
+ autoLoadParent?: boolean
81
+ /** Custom ID extraction function */
82
+ getId?: (() => string | number | null) | null
83
+ /** Transform hook for loaded data */
84
+ transformLoad?: (data: unknown) => T
85
+ /** Callback on successful load */
86
+ onLoadSuccess?: ((data: T) => Promise<void> | void) | null
87
+ /** Callback on load error */
88
+ onLoadError?: ((error: unknown) => Promise<void> | void) | null
89
+ }
90
+
91
+ /**
92
+ * Return type for useEntityItemPage
93
+ */
94
+ export interface UseEntityItemPageReturn<T = unknown> {
95
+ // State
96
+ data: Ref<T | null>
97
+ loading: Ref<boolean>
98
+ error: Ref<string | null>
99
+
100
+ // Computed
101
+ entityId: ComputedRef<string | number | null>
102
+ entityLabel: ComputedRef<string | null>
103
+ isLoaded: ComputedRef<boolean>
104
+
105
+ // Parent chain
106
+ parentConfig: ComputedRef<ParentConfig | null>
107
+ parentId: ComputedRef<string | number | null>
108
+ parentData: ComputedRef<unknown | null>
109
+ parentChain: Ref<Map<number, unknown>>
110
+ parentLoading: Ref<boolean>
111
+ loadParent: () => Promise<void>
112
+ loadParentChain: () => Promise<void>
113
+ getParentManager: () => EntityManager | null
114
+ getChainDepth: (config?: ParentConfig | null) => number
115
+ getInitialDataWithParent: () => Record<string, unknown>
116
+
117
+ // Actions
118
+ load: (id?: string | number | null) => Promise<T | null>
119
+ reload: () => Promise<T | null>
120
+
121
+ // References
122
+ manager: EntityManager
123
+ orchestrator: Orchestrator
124
+
125
+ // Stack hydrator
126
+ hydrator: StackHydratorReturn
127
+ }
128
+
129
+ export function useEntityItemPage<T = unknown>(config: UseEntityItemPageOptions<T>): UseEntityItemPageReturn<T> {
130
+ const {
131
+ entity,
132
+ // Loading options
133
+ loadOnMount = true,
134
+ // Parent chain options
135
+ autoLoadParent = true, // Auto-load parent entity from route.meta.parent
136
+ // ID extraction (custom function for special cases, otherwise uses manager.idField)
137
+ getId = null,
138
+ // Transform hook
139
+ transformLoad = ((data: unknown) => data) as (data: unknown) => T,
140
+ // Callbacks
141
+ onLoadSuccess = null,
142
+ onLoadError = null,
143
+ } = config
144
+
145
+ const route = useRoute()
146
+
147
+ // Get EntityManager via orchestrator
148
+ const orchestrator = inject<Orchestrator | null>('qdadmOrchestrator')
149
+ if (!orchestrator) {
150
+ throw new Error(
151
+ '[qdadm] Orchestrator not provided.\n' +
152
+ 'Possible causes:\n' +
153
+ '1. Kernel not initialized - ensure createKernel().createApp() is called before mounting\n' +
154
+ '2. Component used outside of qdadm app context\n' +
155
+ '3. Missing entityFactory in Kernel options'
156
+ )
157
+ }
158
+ const manager = orchestrator.get(entity)
159
+
160
+ // Provide entity context for child components
161
+ provide('mainEntity', entity)
162
+
163
+ // Stack hydrator for setting entity data on navigation context
164
+ const hydrator = useStackHydrator()
165
+
166
+ // ============ STATE ============
167
+
168
+ const data = ref<T | null>(null) as Ref<T | null>
169
+ const loading = ref(false)
170
+ const error = ref<string | null>(null)
171
+
172
+ // ============ PARENT CHAIN ============
173
+
174
+ /**
175
+ * Parent configuration from route.meta.parent
176
+ * Structure: { entity, param, foreignKey, parent?: {...} }
177
+ * Supports nested parents for N-level chains
178
+ */
179
+ const parentConfig = computed(() => (route.meta?.parent as ParentConfig) || null)
180
+
181
+ /**
182
+ * Parent entity ID from route params (based on parentConfig.param)
183
+ */
184
+ const parentId = computed(() => {
185
+ if (!parentConfig.value) return null
186
+ return (route.params[parentConfig.value.param] as string | number) || null
187
+ })
188
+
189
+ /**
190
+ * Loaded parent entities data (Map: level -> data)
191
+ * Level 1 = immediate parent, Level 2 = grandparent, etc.
192
+ */
193
+ const parentChain = ref<Map<number, unknown>>(new Map())
194
+ const parentLoading = ref(false)
195
+
196
+ /**
197
+ * Immediate parent data (shortcut for level 1)
198
+ */
199
+ const parentData = computed(() => parentChain.value.get(1) || null)
200
+
201
+ /**
202
+ * Calculate the depth of the current entity in the parent chain
203
+ * Returns number of parent levels + 1 for current entity
204
+ */
205
+ function getChainDepth(cfg: ParentConfig | null = parentConfig.value): number {
206
+ if (!cfg) return 1
207
+ let depth = 1
208
+ let current: ParentConfig | undefined = cfg
209
+ while (current) {
210
+ depth++
211
+ current = current.parent
212
+ }
213
+ return depth
214
+ }
215
+
216
+ /**
217
+ * Get parent entity manager (if parent is configured)
218
+ */
219
+ function getParentManager(): EntityManager | null {
220
+ if (!parentConfig.value) return null
221
+ return orchestrator!.get(parentConfig.value.entity)
222
+ }
223
+
224
+ /**
225
+ * Load entire parent chain recursively
226
+ * Sets breadcrumb entities at correct levels (1 = top ancestor, N = immediate parent)
227
+ */
228
+ async function loadParentChain(): Promise<void> {
229
+ if (!parentConfig.value || !parentId.value) return
230
+
231
+ parentLoading.value = true
232
+ const newChain = new Map<number, unknown>()
233
+
234
+ try {
235
+ // Calculate total depth to set correct breadcrumb levels
236
+ // If chain is: grandparent -> parent -> current
237
+ // grandparent = level 1, parent = level 2, current = level 3
238
+ // const totalDepth = getChainDepth()
239
+
240
+ // Load chain from immediate parent up to root
241
+ let currentConfig: ParentConfig | undefined = parentConfig.value
242
+ let level = 1 // Level in our chain (1 = immediate parent)
243
+
244
+ while (currentConfig) {
245
+ const parentEntityId = route.params[currentConfig.param] as string
246
+ if (!parentEntityId) break
247
+
248
+ const parentManager = orchestrator!.get(currentConfig.entity)
249
+ if (!parentManager) {
250
+ console.warn(`[useEntityItemPage] Parent manager not found: ${currentConfig.entity}`)
251
+ break
252
+ }
253
+
254
+ const loadedData = await parentManager.get(parentEntityId)
255
+ if (loadedData) {
256
+ newChain.set(level, loadedData)
257
+
258
+ // Update active stack
259
+ hydrator.setEntityData(currentConfig.entity, loadedData)
260
+ }
261
+
262
+ currentConfig = currentConfig.parent
263
+ level++
264
+ }
265
+
266
+ parentChain.value = newChain
267
+ } catch (err) {
268
+ console.warn('[useEntityItemPage] Failed to load parent chain:', err)
269
+ } finally {
270
+ parentLoading.value = false
271
+ }
272
+ }
273
+
274
+ // Legacy alias for backwards compatibility
275
+ const loadParent = loadParentChain
276
+
277
+ /**
278
+ * Get initial data with parent foreignKey auto-populated
279
+ * Useful for form pages creating child entities
280
+ */
281
+ function getInitialDataWithParent(): Record<string, unknown> {
282
+ const baseData = manager.getInitialData()
283
+
284
+ if (!parentConfig.value || !parentId.value) {
285
+ return baseData
286
+ }
287
+
288
+ // Auto-populate foreignKey with parent ID
289
+ return {
290
+ ...baseData,
291
+ [parentConfig.value.foreignKey || '']: parentId.value,
292
+ }
293
+ }
294
+
295
+ // ============ ID EXTRACTION ============
296
+
297
+ /**
298
+ * Extract entity ID from route params
299
+ * Uses manager.idField as route param name (single source of truth)
300
+ * Supports custom getId function for special cases
301
+ */
302
+ const entityId = computed(() => {
303
+ if (getId) return getId()
304
+ // Use manager.idField as route param name, with fallbacks for common patterns
305
+ return (
306
+ (route.params[manager.idField] as string | number) ||
307
+ (route.params.id as string | number) ||
308
+ (route.params.key as string | number) ||
309
+ null
310
+ )
311
+ })
312
+
313
+ // ============ LOADING ============
314
+
315
+ /**
316
+ * Load entity by ID
317
+ * @param id - Optional ID override (defaults to route param)
318
+ * @returns Loaded entity data or null on error
319
+ */
320
+ async function load(id: string | number | null = entityId.value): Promise<T | null> {
321
+ if (!id) {
322
+ error.value = 'No entity ID provided'
323
+ return null
324
+ }
325
+
326
+ loading.value = true
327
+ error.value = null
328
+
329
+ try {
330
+ const responseData = await manager.get(id)
331
+
332
+ if (!responseData) {
333
+ error.value = `${manager.label || entity} not found`
334
+ return null
335
+ }
336
+
337
+ const transformed = transformLoad(responseData) as T
338
+ data.value = transformed
339
+
340
+ // Update active stack
341
+ hydrator.setCurrentData(transformed)
342
+
343
+ if (onLoadSuccess) {
344
+ await onLoadSuccess(transformed)
345
+ }
346
+
347
+ return transformed
348
+ } catch (err) {
349
+ console.error(`[useEntityItemPage] Failed to load ${entity}:`, err)
350
+ const axiosError = err as { response?: { data?: { detail?: string } }; message?: string }
351
+ error.value =
352
+ axiosError.response?.data?.detail ||
353
+ axiosError.message ||
354
+ `Failed to load ${manager.label || entity}`
355
+
356
+ if (onLoadError) {
357
+ await onLoadError(err)
358
+ }
359
+
360
+ return null
361
+ } finally {
362
+ loading.value = false
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Reload current entity
368
+ */
369
+ async function reload(): Promise<T | null> {
370
+ return load(entityId.value)
371
+ }
372
+
373
+ // ============ COMPUTED ============
374
+
375
+ /**
376
+ * Entity label (uses manager.getEntityLabel)
377
+ */
378
+ const entityLabel = computed(() => {
379
+ if (!data.value) return null
380
+ return manager.getEntityLabel(data.value)
381
+ })
382
+
383
+ /**
384
+ * Check if entity is loaded
385
+ */
386
+ const isLoaded = computed(() => data.value !== null)
387
+
388
+ // ============ LIFECYCLE ============
389
+
390
+ onMounted(async () => {
391
+ // Auto-load parent entity if configured
392
+ if (autoLoadParent && parentConfig.value && parentId.value) {
393
+ await loadParent()
394
+ }
395
+
396
+ // Load current entity
397
+ if (loadOnMount && entityId.value) {
398
+ load()
399
+ }
400
+ })
401
+
402
+ // ============ RETURN ============
403
+
404
+ return {
405
+ // State
406
+ data,
407
+ loading,
408
+ error,
409
+
410
+ // Computed
411
+ entityId,
412
+ entityLabel,
413
+ isLoaded,
414
+
415
+ // Parent chain (supports N-level nesting)
416
+ parentConfig,
417
+ parentId,
418
+ parentData, // Immediate parent (level 1)
419
+ parentChain, // All parents: Map(level -> data)
420
+ parentLoading,
421
+ loadParent, // Alias for loadParentChain
422
+ loadParentChain,
423
+ getParentManager,
424
+ getChainDepth,
425
+ getInitialDataWithParent,
426
+
427
+ // Actions
428
+ load,
429
+ reload,
430
+
431
+ // References (for parent composables)
432
+ manager,
433
+ orchestrator,
434
+
435
+ // Stack hydrator (for setting entity data on navigation context)
436
+ hydrator,
437
+ }
438
+ }