@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,722 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEntityItemShowPage - Unified procedural builder for read-only detail pages
|
|
3
|
+
*
|
|
4
|
+
* Provides a declarative/procedural API to build show pages with:
|
|
5
|
+
* - Auto-load entity data from route params
|
|
6
|
+
* - Auto-generate display fields from EntityManager.fields schema
|
|
7
|
+
* - Permission-aware actions (edit, delete, back)
|
|
8
|
+
* - ShowPage component binding via props/events
|
|
9
|
+
*
|
|
10
|
+
* ## Basic Usage
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const show = useEntityItemShowPage({ entity: 'books' })
|
|
14
|
+
* show.generateFields()
|
|
15
|
+
* show.addEditAction()
|
|
16
|
+
* show.addBackAction()
|
|
17
|
+
*
|
|
18
|
+
* <ShowPage v-bind="show.props.value" v-on="show.events">
|
|
19
|
+
* <template #fields>
|
|
20
|
+
* <ShowField v-for="f in show.fields.value" :key="f.name" :field="f" :value="show.data.value[f.name]" />
|
|
21
|
+
* </template>
|
|
22
|
+
* </ShowPage>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* ## Auto-render mode
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* const show = useEntityItemShowPage({ entity: 'books' })
|
|
29
|
+
* show.generateFields()
|
|
30
|
+
* show.addEditAction()
|
|
31
|
+
*
|
|
32
|
+
* // ShowPage can auto-render fields when data is passed
|
|
33
|
+
* <ShowPage v-bind="show.props.value" v-on="show.events" />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
import { ref, computed, watch, type Ref, type ComputedRef } from 'vue'
|
|
37
|
+
import { useRouter, useRoute, type Router } from 'vue-router'
|
|
38
|
+
import { useConfirm } from 'primevue/useconfirm'
|
|
39
|
+
import {
|
|
40
|
+
useEntityItemPage,
|
|
41
|
+
type ParentConfig,
|
|
42
|
+
type EntityManager,
|
|
43
|
+
type UseEntityItemPageReturn,
|
|
44
|
+
} from './useEntityItemPage'
|
|
45
|
+
import type { StackHydratorReturn } from '../chain/useStackHydrator'
|
|
46
|
+
import {
|
|
47
|
+
useFieldManager,
|
|
48
|
+
snakeCaseToTitle,
|
|
49
|
+
type FieldGroup,
|
|
50
|
+
type GroupDefinition,
|
|
51
|
+
type GroupOptions,
|
|
52
|
+
type AddFieldOptions,
|
|
53
|
+
} from './useFieldManager'
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Orchestrator interface
|
|
57
|
+
*/
|
|
58
|
+
interface Orchestrator {
|
|
59
|
+
get: (entityName: string) => EntityManager
|
|
60
|
+
toast?: {
|
|
61
|
+
success: (summary: string, detail?: string, emitter?: unknown) => void
|
|
62
|
+
error: (summary: string, detail?: string, emitter?: unknown) => void
|
|
63
|
+
warn: (summary: string, detail?: string, emitter?: unknown) => void
|
|
64
|
+
info: (summary: string, detail?: string, emitter?: unknown) => void
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Field definition from EntityManager schema
|
|
70
|
+
*/
|
|
71
|
+
export interface FieldDefinition {
|
|
72
|
+
name: string
|
|
73
|
+
type?: string
|
|
74
|
+
schemaType?: string
|
|
75
|
+
label?: string
|
|
76
|
+
reference?: string
|
|
77
|
+
referenceRoute?: string | ((value: unknown) => { name: string; params: Record<string, unknown> })
|
|
78
|
+
referenceLabel?: string | ((value: unknown, option?: unknown) => string)
|
|
79
|
+
options?: unknown[]
|
|
80
|
+
optionLabel?: string
|
|
81
|
+
optionValue?: string
|
|
82
|
+
// Display options
|
|
83
|
+
dateFormat?: string
|
|
84
|
+
currencyCode?: string
|
|
85
|
+
locale?: string
|
|
86
|
+
booleanLabels?: { true: string; false: string }
|
|
87
|
+
severity?: string | ((value: unknown) => string | { severity: string; icon?: string; label?: string })
|
|
88
|
+
imageWidth?: string
|
|
89
|
+
imageHeight?: string
|
|
90
|
+
render?: (value: unknown) => string
|
|
91
|
+
[key: string]: unknown
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolved field configuration for display rendering
|
|
96
|
+
*/
|
|
97
|
+
export interface ResolvedFieldConfig extends FieldDefinition {
|
|
98
|
+
name: string
|
|
99
|
+
type: string
|
|
100
|
+
schemaType: string
|
|
101
|
+
label: string
|
|
102
|
+
group?: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// FieldGroup, GroupDefinition, GroupOptions imported from useFieldManager
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Action configuration
|
|
109
|
+
*/
|
|
110
|
+
export interface ActionConfig {
|
|
111
|
+
name: string
|
|
112
|
+
label: string
|
|
113
|
+
icon?: string
|
|
114
|
+
severity?: string
|
|
115
|
+
onClick: () => void | Promise<void>
|
|
116
|
+
visible?: (ctx: { canEdit: boolean; canDelete: boolean }) => boolean
|
|
117
|
+
disabled?: (ctx: { loading: boolean }) => boolean
|
|
118
|
+
loading?: () => boolean
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Lazy action configuration
|
|
123
|
+
*
|
|
124
|
+
* Unlike regular actions whose visibility is derived from entity data on the model,
|
|
125
|
+
* lazy actions resolve asynchronously after entity load (e.g., fetching security info
|
|
126
|
+
* from a separate endpoint). Hidden by default until resolve() returns visible: true.
|
|
127
|
+
*/
|
|
128
|
+
export interface LazyActionConfig {
|
|
129
|
+
name: string
|
|
130
|
+
label: string
|
|
131
|
+
icon?: string
|
|
132
|
+
severity?: string
|
|
133
|
+
onClick: () => void | Promise<void>
|
|
134
|
+
resolve: (data: unknown) => Promise<{ visible?: boolean; disabled?: boolean }>
|
|
135
|
+
loading?: () => boolean
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Resolved action for rendering
|
|
140
|
+
*/
|
|
141
|
+
export interface ResolvedAction extends ActionConfig {
|
|
142
|
+
isLoading: boolean
|
|
143
|
+
isDisabled: boolean
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Page title parts for PageHeader
|
|
148
|
+
*/
|
|
149
|
+
interface PageTitleParts {
|
|
150
|
+
action: string
|
|
151
|
+
entityName: string | undefined
|
|
152
|
+
entityLabel: string | undefined
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Show props for ShowPage component
|
|
157
|
+
*/
|
|
158
|
+
export interface ShowPageProps {
|
|
159
|
+
loading: boolean
|
|
160
|
+
title: string
|
|
161
|
+
titleParts: PageTitleParts
|
|
162
|
+
badges: Array<{ label: string; severity?: string }>
|
|
163
|
+
fields: ResolvedFieldConfig[]
|
|
164
|
+
groups: FieldGroup<ResolvedFieldConfig>[]
|
|
165
|
+
data: Record<string, unknown> | null
|
|
166
|
+
actions: ResolvedAction[]
|
|
167
|
+
fetchError: string | null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Show events for ShowPage component
|
|
172
|
+
*/
|
|
173
|
+
export interface ShowPageEvents {
|
|
174
|
+
edit: () => void
|
|
175
|
+
delete: () => void
|
|
176
|
+
back: () => void
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Type mapping from schema types to display types
|
|
181
|
+
*/
|
|
182
|
+
const TYPE_MAPPINGS: Record<string, string> = {
|
|
183
|
+
text: 'text',
|
|
184
|
+
string: 'text',
|
|
185
|
+
email: 'email',
|
|
186
|
+
password: 'password',
|
|
187
|
+
number: 'number',
|
|
188
|
+
integer: 'number',
|
|
189
|
+
float: 'number',
|
|
190
|
+
decimal: 'number',
|
|
191
|
+
currency: 'currency',
|
|
192
|
+
boolean: 'boolean',
|
|
193
|
+
bool: 'boolean',
|
|
194
|
+
date: 'date',
|
|
195
|
+
datetime: 'datetime',
|
|
196
|
+
time: 'datetime',
|
|
197
|
+
timestamp: 'datetime',
|
|
198
|
+
select: 'select',
|
|
199
|
+
enum: 'select',
|
|
200
|
+
textarea: 'textarea',
|
|
201
|
+
longtext: 'textarea',
|
|
202
|
+
reference: 'reference',
|
|
203
|
+
foreignKey: 'reference',
|
|
204
|
+
image: 'image',
|
|
205
|
+
url: 'url',
|
|
206
|
+
link: 'url',
|
|
207
|
+
json: 'json',
|
|
208
|
+
object: 'json',
|
|
209
|
+
array: 'json',
|
|
210
|
+
badge: 'badge',
|
|
211
|
+
tag: 'badge',
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// snakeCaseToTitle imported from useFieldManager
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Options for useEntityItemShowPage
|
|
218
|
+
*/
|
|
219
|
+
export interface UseEntityItemShowPageOptions<T = unknown> {
|
|
220
|
+
/** Entity name */
|
|
221
|
+
entity: string
|
|
222
|
+
/** Auto-load entity on mount (default: true) */
|
|
223
|
+
loadOnMount?: boolean
|
|
224
|
+
/** Transform hook for loaded data */
|
|
225
|
+
transformLoad?: (data: unknown) => T
|
|
226
|
+
/** Callback on successful load */
|
|
227
|
+
onLoadSuccess?: ((data: T) => Promise<void> | void) | null
|
|
228
|
+
/** Callback on load error */
|
|
229
|
+
onLoadError?: ((error: unknown) => Promise<void> | void) | null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Options for generateFields
|
|
234
|
+
*/
|
|
235
|
+
export interface GenerateFieldsOptions {
|
|
236
|
+
/** Only include these fields */
|
|
237
|
+
only?: string[] | null
|
|
238
|
+
/** Exclude these fields */
|
|
239
|
+
exclude?: string[]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// AddFieldOptions imported from useFieldManager
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Return type for useEntityItemShowPage
|
|
246
|
+
*/
|
|
247
|
+
export interface UseEntityItemShowPageReturn<T = unknown> {
|
|
248
|
+
// State from base
|
|
249
|
+
data: Ref<T | null>
|
|
250
|
+
loading: Ref<boolean>
|
|
251
|
+
error: Ref<string | null>
|
|
252
|
+
entityId: ComputedRef<string | number | null>
|
|
253
|
+
entityLabel: ComputedRef<string | null>
|
|
254
|
+
isLoaded: ComputedRef<boolean>
|
|
255
|
+
|
|
256
|
+
// Parent chain
|
|
257
|
+
parentConfig: ComputedRef<ParentConfig | null>
|
|
258
|
+
parentId: ComputedRef<string | number | null>
|
|
259
|
+
parentData: ComputedRef<unknown | null>
|
|
260
|
+
|
|
261
|
+
// Field management
|
|
262
|
+
fields: ComputedRef<ResolvedFieldConfig[]>
|
|
263
|
+
generateFields: (options?: GenerateFieldsOptions) => UseEntityItemShowPageReturn<T>
|
|
264
|
+
addField: (name: string, fieldConfig: Partial<FieldDefinition>, options?: AddFieldOptions) => UseEntityItemShowPageReturn<T>
|
|
265
|
+
updateField: (name: string, updates: Partial<FieldDefinition>) => UseEntityItemShowPageReturn<T>
|
|
266
|
+
removeField: (name: string) => UseEntityItemShowPageReturn<T>
|
|
267
|
+
excludeField: (name: string) => UseEntityItemShowPageReturn<T>
|
|
268
|
+
reorderFields: (fieldNames: string[]) => UseEntityItemShowPageReturn<T>
|
|
269
|
+
getField: (name: string) => ResolvedFieldConfig | undefined
|
|
270
|
+
|
|
271
|
+
// Group management
|
|
272
|
+
groups: ComputedRef<FieldGroup<ResolvedFieldConfig>[]>
|
|
273
|
+
group: (name: string, fieldsOrOptions?: string[] | GroupOptions, options?: GroupOptions) => UseEntityItemShowPageReturn<T>
|
|
274
|
+
defineGroups: (definitions: Record<string, GroupDefinition>) => UseEntityItemShowPageReturn<T>
|
|
275
|
+
getGroup: (name: string) => FieldGroup<ResolvedFieldConfig> | undefined
|
|
276
|
+
|
|
277
|
+
// Action management
|
|
278
|
+
actions: ComputedRef<ResolvedAction[]>
|
|
279
|
+
addAction: (action: ActionConfig) => UseEntityItemShowPageReturn<T>
|
|
280
|
+
addLazyAction: (action: LazyActionConfig) => UseEntityItemShowPageReturn<T>
|
|
281
|
+
addEditAction: (options?: { label?: string; icon?: string }) => UseEntityItemShowPageReturn<T>
|
|
282
|
+
addDeleteAction: (options?: { label?: string; icon?: string; confirm?: boolean }) => UseEntityItemShowPageReturn<T>
|
|
283
|
+
addBackAction: (options?: { label?: string; icon?: string; route?: string }) => UseEntityItemShowPageReturn<T>
|
|
284
|
+
removeAction: (name: string) => UseEntityItemShowPageReturn<T>
|
|
285
|
+
|
|
286
|
+
// Permissions
|
|
287
|
+
canEdit: ComputedRef<boolean>
|
|
288
|
+
canDelete: ComputedRef<boolean>
|
|
289
|
+
|
|
290
|
+
// Page title
|
|
291
|
+
title: ComputedRef<string>
|
|
292
|
+
titleParts: ComputedRef<PageTitleParts>
|
|
293
|
+
|
|
294
|
+
// Component binding
|
|
295
|
+
props: ComputedRef<ShowPageProps>
|
|
296
|
+
events: ShowPageEvents
|
|
297
|
+
|
|
298
|
+
// Actions
|
|
299
|
+
load: (id?: string | number | null) => Promise<T | null>
|
|
300
|
+
reload: () => Promise<T | null>
|
|
301
|
+
goToEdit: () => void
|
|
302
|
+
goBack: () => void
|
|
303
|
+
deleteEntity: () => Promise<void>
|
|
304
|
+
|
|
305
|
+
// References
|
|
306
|
+
manager: EntityManager
|
|
307
|
+
orchestrator: Orchestrator
|
|
308
|
+
hydrator: StackHydratorReturn
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Confirm service interface
|
|
313
|
+
*/
|
|
314
|
+
interface ConfirmService {
|
|
315
|
+
require: (options: {
|
|
316
|
+
message: string
|
|
317
|
+
header: string
|
|
318
|
+
icon: string
|
|
319
|
+
acceptClass?: string
|
|
320
|
+
accept: () => void
|
|
321
|
+
}) => void
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function useEntityItemShowPage<T = unknown>(
|
|
325
|
+
options: UseEntityItemShowPageOptions<T>
|
|
326
|
+
): UseEntityItemShowPageReturn<T> {
|
|
327
|
+
const { entity, loadOnMount = true, transformLoad, onLoadSuccess, onLoadError } = options
|
|
328
|
+
|
|
329
|
+
const router: Router = useRouter()
|
|
330
|
+
const route = useRoute()
|
|
331
|
+
const confirm = useConfirm() as ConfirmService
|
|
332
|
+
|
|
333
|
+
// Lazy action state (declared before base so onLoadSuccess wrapper can reference it)
|
|
334
|
+
const lazyActionsMap = ref<Map<string, LazyActionConfig>>(new Map())
|
|
335
|
+
const lazyResolvedStates = ref<Map<string, { visible: boolean; disabled: boolean }>>(new Map())
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Run all lazy action resolvers with the loaded entity data.
|
|
339
|
+
* Errors in individual resolvers are caught — action stays hidden.
|
|
340
|
+
*/
|
|
341
|
+
async function runLazyResolvers(entityData: unknown): Promise<void> {
|
|
342
|
+
const newStates = new Map<string, { visible: boolean; disabled: boolean }>()
|
|
343
|
+
const entries = Array.from(lazyActionsMap.value.entries())
|
|
344
|
+
await Promise.all(
|
|
345
|
+
entries.map(async ([name, action]) => {
|
|
346
|
+
try {
|
|
347
|
+
const result = await action.resolve(entityData)
|
|
348
|
+
newStates.set(name, {
|
|
349
|
+
visible: result.visible ?? false,
|
|
350
|
+
disabled: result.disabled ?? false,
|
|
351
|
+
})
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.warn(`[useEntityItemShowPage] Lazy action "${name}" resolve failed:`, err)
|
|
354
|
+
newStates.set(name, { visible: false, disabled: false })
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
)
|
|
358
|
+
lazyResolvedStates.value = newStates
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Use base composable — wrap onLoadSuccess to run lazy resolvers after entity load
|
|
362
|
+
const base = useEntityItemPage<T>({
|
|
363
|
+
entity,
|
|
364
|
+
loadOnMount,
|
|
365
|
+
transformLoad,
|
|
366
|
+
onLoadSuccess: async (entityData) => {
|
|
367
|
+
await runLazyResolvers(entityData)
|
|
368
|
+
if (onLoadSuccess) await onLoadSuccess(entityData)
|
|
369
|
+
},
|
|
370
|
+
onLoadError,
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
const { manager, orchestrator: baseOrchestrator, data, loading, error, entityId, entityLabel, isLoaded, hydrator } = base
|
|
374
|
+
// Cast orchestrator to include toast methods
|
|
375
|
+
const orchestrator = baseOrchestrator as Orchestrator
|
|
376
|
+
|
|
377
|
+
// ============ FIELD & GROUP MANAGEMENT (via useFieldManager) ============
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Show-specific field resolver with reference route auto-detection
|
|
381
|
+
*/
|
|
382
|
+
function resolveFieldConfig(name: string, fieldConfig: Partial<FieldDefinition>): ResolvedFieldConfig {
|
|
383
|
+
const {
|
|
384
|
+
type = 'text',
|
|
385
|
+
schemaType = type,
|
|
386
|
+
label = '',
|
|
387
|
+
reference = '',
|
|
388
|
+
severity,
|
|
389
|
+
...rest
|
|
390
|
+
} = fieldConfig
|
|
391
|
+
|
|
392
|
+
let resolvedDisplayType = TYPE_MAPPINGS[type] || TYPE_MAPPINGS[schemaType] || 'text'
|
|
393
|
+
const resolvedLabel = label || snakeCaseToTitle(name)
|
|
394
|
+
|
|
395
|
+
// Auto-set reference route if reference entity is specified
|
|
396
|
+
let referenceRoute = rest.referenceRoute
|
|
397
|
+
if (reference && !referenceRoute) {
|
|
398
|
+
const refManager = orchestrator.get(reference)
|
|
399
|
+
if (refManager) {
|
|
400
|
+
referenceRoute = (value: unknown) => ({
|
|
401
|
+
name: `${refManager.routePrefix || reference}-show`,
|
|
402
|
+
params: { [refManager.idField]: value },
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Auto-inject severity from manager's severity maps
|
|
408
|
+
let resolvedSeverity = severity
|
|
409
|
+
if (!resolvedSeverity && manager.hasSeverityMap?.(name)) {
|
|
410
|
+
resolvedSeverity = manager.getSeverityDescriptor
|
|
411
|
+
? (value: unknown) => manager.getSeverityDescriptor!(name, value as string | number, 'secondary')
|
|
412
|
+
: (value: unknown) => manager.getSeverity!(name, value as string | number, 'secondary')
|
|
413
|
+
if (resolvedDisplayType === 'text') {
|
|
414
|
+
resolvedDisplayType = 'badge'
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
name,
|
|
420
|
+
type: resolvedDisplayType,
|
|
421
|
+
schemaType,
|
|
422
|
+
label: resolvedLabel,
|
|
423
|
+
reference,
|
|
424
|
+
referenceRoute,
|
|
425
|
+
severity: resolvedSeverity,
|
|
426
|
+
...rest,
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Create field manager with show-specific resolver
|
|
431
|
+
const fieldManager = useFieldManager<ResolvedFieldConfig>({
|
|
432
|
+
resolveFieldConfig,
|
|
433
|
+
getSchemaFieldConfig: (name) => manager.getFieldConfig?.(name) || null,
|
|
434
|
+
entity,
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
// Direct access to computed values
|
|
438
|
+
const { fields, groups } = fieldManager
|
|
439
|
+
|
|
440
|
+
// Chainable method wrappers (return returnValue for fluent API)
|
|
441
|
+
const chain = <A extends unknown[], R>(fn: (...args: A) => R) =>
|
|
442
|
+
(...args: A): UseEntityItemShowPageReturn<T> => { fn(...args); return returnValue }
|
|
443
|
+
|
|
444
|
+
const generateFields = (opts?: GenerateFieldsOptions): UseEntityItemShowPageReturn<T> => {
|
|
445
|
+
const formFields = manager.getFormFields?.() || []
|
|
446
|
+
fieldManager.generateFields(formFields, opts)
|
|
447
|
+
return returnValue
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const addField = chain(fieldManager.addField)
|
|
451
|
+
const updateField = chain(fieldManager.updateField)
|
|
452
|
+
const removeField = chain(fieldManager.removeField)
|
|
453
|
+
const excludeField = chain(fieldManager.excludeField)
|
|
454
|
+
const reorderFields = chain(fieldManager.setFieldOrder)
|
|
455
|
+
const group = chain(fieldManager.group)
|
|
456
|
+
const defineGroups = chain(fieldManager.defineGroups)
|
|
457
|
+
|
|
458
|
+
// Direct accessors (no chaining needed)
|
|
459
|
+
const getField = fieldManager.getField
|
|
460
|
+
const getGroup = fieldManager.getGroup
|
|
461
|
+
|
|
462
|
+
// ============ ACTION MANAGEMENT ============
|
|
463
|
+
|
|
464
|
+
const actionsMap = ref<Map<string, ActionConfig>>(new Map())
|
|
465
|
+
const actionOrder = ref<string[]>([])
|
|
466
|
+
|
|
467
|
+
const canEdit = computed(() => manager.canUpdate?.(data.value) ?? true)
|
|
468
|
+
const canDelete = computed(() => manager.canDelete?.(data.value) ?? true)
|
|
469
|
+
|
|
470
|
+
function addAction(action: ActionConfig): UseEntityItemShowPageReturn<T> {
|
|
471
|
+
actionsMap.value.set(action.name, action)
|
|
472
|
+
if (!actionOrder.value.includes(action.name)) {
|
|
473
|
+
actionOrder.value.push(action.name)
|
|
474
|
+
}
|
|
475
|
+
return returnValue
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function addLazyAction(action: LazyActionConfig): UseEntityItemShowPageReturn<T> {
|
|
479
|
+
lazyActionsMap.value.set(action.name, action)
|
|
480
|
+
if (!actionOrder.value.includes(action.name)) {
|
|
481
|
+
actionOrder.value.push(action.name)
|
|
482
|
+
}
|
|
483
|
+
return returnValue
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function addEditAction(editOptions: { label?: string; icon?: string } = {}): UseEntityItemShowPageReturn<T> {
|
|
487
|
+
const { label = 'Edit', icon = 'pi pi-pencil' } = editOptions
|
|
488
|
+
return addAction({
|
|
489
|
+
name: 'edit',
|
|
490
|
+
label,
|
|
491
|
+
icon,
|
|
492
|
+
severity: 'primary',
|
|
493
|
+
onClick: goToEdit,
|
|
494
|
+
visible: ({ canEdit }) => canEdit,
|
|
495
|
+
})
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function addDeleteAction(
|
|
499
|
+
deleteOptions: { label?: string; icon?: string; confirm?: boolean } = {}
|
|
500
|
+
): UseEntityItemShowPageReturn<T> {
|
|
501
|
+
const { label = 'Delete', icon = 'pi pi-trash', confirm: confirmDelete = true } = deleteOptions
|
|
502
|
+
return addAction({
|
|
503
|
+
name: 'delete',
|
|
504
|
+
label,
|
|
505
|
+
icon,
|
|
506
|
+
severity: 'danger',
|
|
507
|
+
onClick: () => {
|
|
508
|
+
if (confirmDelete) {
|
|
509
|
+
confirm.require({
|
|
510
|
+
message: `Are you sure you want to delete this ${manager.label || entity}?`,
|
|
511
|
+
header: 'Confirm Delete',
|
|
512
|
+
icon: 'pi pi-exclamation-triangle',
|
|
513
|
+
acceptClass: 'p-button-danger',
|
|
514
|
+
accept: () => deleteEntity(),
|
|
515
|
+
})
|
|
516
|
+
} else {
|
|
517
|
+
deleteEntity()
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
visible: ({ canDelete }) => canDelete,
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function addBackAction(backOptions: { label?: string; icon?: string; route?: string } = {}): UseEntityItemShowPageReturn<T> {
|
|
525
|
+
const { label = 'Back', icon = 'pi pi-arrow-left', route: backRoute } = backOptions
|
|
526
|
+
return addAction({
|
|
527
|
+
name: 'back',
|
|
528
|
+
label,
|
|
529
|
+
icon,
|
|
530
|
+
severity: 'secondary',
|
|
531
|
+
onClick: () => {
|
|
532
|
+
if (backRoute) {
|
|
533
|
+
router.push({ name: backRoute })
|
|
534
|
+
} else {
|
|
535
|
+
goBack()
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
})
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function removeAction(name: string): UseEntityItemShowPageReturn<T> {
|
|
542
|
+
actionsMap.value.delete(name)
|
|
543
|
+
lazyActionsMap.value.delete(name)
|
|
544
|
+
lazyResolvedStates.value.delete(name)
|
|
545
|
+
const index = actionOrder.value.indexOf(name)
|
|
546
|
+
if (index !== -1) {
|
|
547
|
+
actionOrder.value.splice(index, 1)
|
|
548
|
+
}
|
|
549
|
+
return returnValue
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const actions = computed<ResolvedAction[]>(() => {
|
|
553
|
+
const ctx = { canEdit: canEdit.value, canDelete: canDelete.value, loading: loading.value }
|
|
554
|
+
const resolved = lazyResolvedStates.value // force dependency tracking
|
|
555
|
+
|
|
556
|
+
return actionOrder.value
|
|
557
|
+
.map((name) => {
|
|
558
|
+
const regular = actionsMap.value.get(name)
|
|
559
|
+
if (regular) return { type: 'regular' as const, action: regular }
|
|
560
|
+
const lazy = lazyActionsMap.value.get(name)
|
|
561
|
+
if (lazy) return { type: 'lazy' as const, action: lazy }
|
|
562
|
+
return null
|
|
563
|
+
})
|
|
564
|
+
.filter((entry): entry is NonNullable<typeof entry> => entry !== null)
|
|
565
|
+
.filter((entry) => {
|
|
566
|
+
if (entry.type === 'regular') return !entry.action.visible || entry.action.visible(ctx)
|
|
567
|
+
const state = resolved.get(entry.action.name)
|
|
568
|
+
return state?.visible === true
|
|
569
|
+
})
|
|
570
|
+
.map((entry) => {
|
|
571
|
+
const a = entry.action
|
|
572
|
+
if (entry.type === 'lazy') {
|
|
573
|
+
const state = resolved.get(a.name)
|
|
574
|
+
return { ...a, isLoading: a.loading ? a.loading() : false, isDisabled: state?.disabled ?? false }
|
|
575
|
+
}
|
|
576
|
+
const regular = a as ActionConfig
|
|
577
|
+
return { ...regular, isLoading: regular.loading ? regular.loading() : false, isDisabled: regular.disabled ? regular.disabled(ctx) : false }
|
|
578
|
+
})
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// ============ PAGE TITLE ============
|
|
582
|
+
|
|
583
|
+
const titleParts = computed<PageTitleParts>(() => ({
|
|
584
|
+
action: 'View',
|
|
585
|
+
entityName: manager.label || entity,
|
|
586
|
+
entityLabel: entityLabel.value || undefined,
|
|
587
|
+
}))
|
|
588
|
+
|
|
589
|
+
const entityBadges = computed(() => {
|
|
590
|
+
if (!manager.getEntityBadges) return []
|
|
591
|
+
return manager.getEntityBadges(data.value as any)
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
const title = computed(() => {
|
|
595
|
+
const parts = titleParts.value
|
|
596
|
+
if (parts.entityLabel) {
|
|
597
|
+
return `${parts.entityLabel}`
|
|
598
|
+
}
|
|
599
|
+
return `${parts.action} ${parts.entityName}`
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
// ============ NAVIGATION ============
|
|
603
|
+
|
|
604
|
+
function goToEdit(): void {
|
|
605
|
+
if (!entityId.value) return
|
|
606
|
+
const editRouteName = `${manager.routePrefix || entity}-edit`
|
|
607
|
+
router.push({
|
|
608
|
+
name: editRouteName,
|
|
609
|
+
params: { [manager.idField]: entityId.value },
|
|
610
|
+
})
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function goBack(): void {
|
|
614
|
+
// Try to go to list route
|
|
615
|
+
const listRouteName = manager.routePrefix || entity
|
|
616
|
+
router.push({ name: listRouteName })
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function deleteEntity(): Promise<void> {
|
|
620
|
+
if (!entityId.value) return
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
await manager.delete(entityId.value)
|
|
624
|
+
orchestrator.toast?.success(`${manager.label || entity} deleted`)
|
|
625
|
+
goBack()
|
|
626
|
+
} catch (err) {
|
|
627
|
+
console.error(`[useEntityItemShowPage] Failed to delete ${entity}:`, err)
|
|
628
|
+
const axiosError = err as { response?: { data?: { detail?: string } }; message?: string }
|
|
629
|
+
const errorMessage =
|
|
630
|
+
axiosError.response?.data?.detail || axiosError.message || `Failed to delete ${manager.label || entity}`
|
|
631
|
+
orchestrator.toast?.error(errorMessage)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// ============ COMPONENT BINDING ============
|
|
636
|
+
|
|
637
|
+
const props = computed<ShowPageProps>(() => ({
|
|
638
|
+
loading: loading.value,
|
|
639
|
+
title: title.value,
|
|
640
|
+
titleParts: titleParts.value,
|
|
641
|
+
badges: entityBadges.value,
|
|
642
|
+
fields: fields.value,
|
|
643
|
+
groups: groups.value,
|
|
644
|
+
data: data.value as Record<string, unknown> | null,
|
|
645
|
+
actions: actions.value,
|
|
646
|
+
fetchError: error.value,
|
|
647
|
+
}))
|
|
648
|
+
|
|
649
|
+
const events: ShowPageEvents = {
|
|
650
|
+
edit: goToEdit,
|
|
651
|
+
delete: deleteEntity,
|
|
652
|
+
back: goBack,
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// ============ RETURN ============
|
|
656
|
+
|
|
657
|
+
const returnValue: UseEntityItemShowPageReturn<T> = {
|
|
658
|
+
// State from base
|
|
659
|
+
data,
|
|
660
|
+
loading,
|
|
661
|
+
error,
|
|
662
|
+
entityId,
|
|
663
|
+
entityLabel,
|
|
664
|
+
isLoaded,
|
|
665
|
+
|
|
666
|
+
// Parent chain
|
|
667
|
+
parentConfig: base.parentConfig,
|
|
668
|
+
parentId: base.parentId,
|
|
669
|
+
parentData: base.parentData,
|
|
670
|
+
|
|
671
|
+
// Field management
|
|
672
|
+
fields,
|
|
673
|
+
generateFields,
|
|
674
|
+
addField,
|
|
675
|
+
updateField,
|
|
676
|
+
removeField,
|
|
677
|
+
excludeField,
|
|
678
|
+
reorderFields,
|
|
679
|
+
getField,
|
|
680
|
+
|
|
681
|
+
// Group management
|
|
682
|
+
groups,
|
|
683
|
+
group,
|
|
684
|
+
defineGroups,
|
|
685
|
+
getGroup,
|
|
686
|
+
|
|
687
|
+
// Action management
|
|
688
|
+
actions,
|
|
689
|
+
addAction,
|
|
690
|
+
addLazyAction,
|
|
691
|
+
addEditAction,
|
|
692
|
+
addDeleteAction,
|
|
693
|
+
addBackAction,
|
|
694
|
+
removeAction,
|
|
695
|
+
|
|
696
|
+
// Permissions
|
|
697
|
+
canEdit,
|
|
698
|
+
canDelete,
|
|
699
|
+
|
|
700
|
+
// Page title
|
|
701
|
+
title,
|
|
702
|
+
titleParts,
|
|
703
|
+
|
|
704
|
+
// Component binding
|
|
705
|
+
props,
|
|
706
|
+
events,
|
|
707
|
+
|
|
708
|
+
// Actions
|
|
709
|
+
load: base.load,
|
|
710
|
+
reload: base.reload,
|
|
711
|
+
goToEdit,
|
|
712
|
+
goBack,
|
|
713
|
+
deleteEntity,
|
|
714
|
+
|
|
715
|
+
// References
|
|
716
|
+
manager,
|
|
717
|
+
orchestrator,
|
|
718
|
+
hydrator,
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return returnValue
|
|
722
|
+
}
|