@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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- 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
|
+
}
|