@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,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
+ }