@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,431 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ShowPage - Unified show/detail page component
4
+ *
5
+ * Renders a complete read-only detail page with:
6
+ * - PageHeader with title and action buttons
7
+ * - Loading and error states
8
+ * - Optional media zone (for images, avatars, flags)
9
+ * - Field content via slots
10
+ * - Action footer (edit, delete, back)
11
+ *
12
+ * Props come from useEntityItemShowPage composable:
13
+ *
14
+ * ```vue
15
+ * const show = useEntityItemShowPage({ entity: 'books' })
16
+ * show.generateFields()
17
+ * show.addEditAction()
18
+ * show.addBackAction()
19
+ *
20
+ * <ShowPage v-bind="show.props.value" v-on="show.events">
21
+ * <template #media>
22
+ * <img :src="show.data.value?.thumbnail" />
23
+ * </template>
24
+ * <template #fields>
25
+ * <ShowField v-for="f in show.fields.value" :key="f.name" :field="f" :value="show.data.value[f.name]" />
26
+ * </template>
27
+ * </ShowPage>
28
+ * ```
29
+ *
30
+ * Slots:
31
+ * - #nav: PageNav for breadcrumb customization
32
+ * - #toolbar: Custom toolbar actions (between header and content)
33
+ * - #header-actions: Custom header action buttons
34
+ * - #media: Media zone (image, avatar, flag) - renders in left column
35
+ * - #fields: Field content (required) - renders in right column (or full width if no media)
36
+ * - #footer: Custom footer (replaces ShowActions if provided)
37
+ * - #error: Custom error display
38
+ * - #loading: Custom loading display
39
+ */
40
+ import { computed, type PropType } from 'vue'
41
+ import PageHeader from '../layout/PageHeader.vue'
42
+ import Card from 'primevue/card'
43
+ import Button from 'primevue/button'
44
+ import Message from 'primevue/message'
45
+ import FieldGroups from '../item/FieldGroups.vue'
46
+ import ShowField from './ShowField.vue'
47
+
48
+ /**
49
+ * Page title parts for PageHeader
50
+ */
51
+ interface PageTitleParts {
52
+ action: string
53
+ entityName: string | undefined
54
+ entityLabel: string | undefined
55
+ }
56
+
57
+ /**
58
+ * Action configuration
59
+ */
60
+ interface ResolvedAction {
61
+ name: string
62
+ label: string
63
+ icon?: string
64
+ severity?: string
65
+ isLoading: boolean
66
+ isDisabled: boolean
67
+ onClick: () => void | Promise<void>
68
+ }
69
+
70
+ /**
71
+ * Resolved field config
72
+ */
73
+ interface ResolvedFieldConfig {
74
+ name: string
75
+ type: string
76
+ label: string
77
+ [key: string]: unknown
78
+ }
79
+
80
+ /**
81
+ * Fetch error type
82
+ */
83
+ interface FetchError {
84
+ message?: string
85
+ detail?: string
86
+ }
87
+
88
+ /**
89
+ * Field group structure
90
+ */
91
+ interface FieldGroup {
92
+ name: string
93
+ label: string
94
+ fields: ResolvedFieldConfig[]
95
+ children: FieldGroup[]
96
+ parent?: string
97
+ }
98
+
99
+ /**
100
+ * Layout mode for groups
101
+ */
102
+ type LayoutMode = 'flat' | 'sections' | 'cards' | 'tabs' | 'accordion'
103
+
104
+ const props = defineProps({
105
+ // State
106
+ loading: { type: Boolean, default: false },
107
+
108
+ // Title (use title OR titleParts)
109
+ title: { type: String as PropType<string | null>, default: null },
110
+ titleParts: { type: Object as PropType<PageTitleParts | null>, default: null },
111
+
112
+ // Custom badges from entity manager
113
+ badges: { type: Array as PropType<Array<{ label: string; severity?: string }>>, default: () => [] },
114
+
115
+ // Fields (for auto-rendering - optional, can use #fields slot instead)
116
+ fields: { type: Array as PropType<ResolvedFieldConfig[]>, default: () => [] },
117
+
118
+ // Groups (hierarchical field organization)
119
+ groups: { type: Array as PropType<FieldGroup[]>, default: () => [] },
120
+
121
+ // Data (entity data for field values)
122
+ data: { type: Object as PropType<Record<string, unknown> | null>, default: null },
123
+
124
+ // Actions (from builder.actions)
125
+ actions: { type: Array as PropType<ResolvedAction[]>, default: () => [] },
126
+
127
+ // Error for fetch failures
128
+ fetchError: { type: [String, Object] as PropType<string | FetchError | null>, default: null },
129
+
130
+ // UI options
131
+ showActions: { type: Boolean, default: true },
132
+ cardWrapper: { type: Boolean, default: true },
133
+ horizontalFields: { type: Boolean, default: true },
134
+ labelWidth: { type: String, default: '140px' },
135
+ // Media zone options
136
+ mediaWidth: { type: String, default: '200px' },
137
+
138
+ // Group layout mode: flat, sections, cards, tabs, accordion
139
+ layout: { type: String as PropType<LayoutMode>, default: 'flat' },
140
+
141
+ // Child group layout mode (for nested groups)
142
+ childLayout: { type: String as PropType<LayoutMode>, default: 'sections' }
143
+ })
144
+
145
+ // Check if media slot is used
146
+ const slots = defineSlots<{
147
+ nav?: () => unknown
148
+ toolbar?: () => unknown
149
+ 'header-actions'?: () => unknown
150
+ media?: () => unknown
151
+ fields?: () => unknown
152
+ groups?: () => unknown
153
+ footer?: () => unknown
154
+ error?: (props: { error: unknown }) => unknown
155
+ loading?: () => unknown
156
+ }>()
157
+
158
+ // Determine if we should use group rendering
159
+ const useGroupLayout = computed(() => {
160
+ // Use groups if: layout is not flat, or groups are explicitly defined (not just _default)
161
+ const hasRealGroups = props.groups.length > 0 && !(props.groups.length === 1 && props.groups[0]?.name === '_default')
162
+ return hasRealGroups && props.layout !== 'flat'
163
+ })
164
+
165
+ const emit = defineEmits<{
166
+ (e: 'edit'): void
167
+ (e: 'delete'): void
168
+ (e: 'back'): void
169
+ }>()
170
+
171
+ // Header actions: actions that go in the header (e.g., edit)
172
+ const headerActions = computed<ResolvedAction[]>(() =>
173
+ props.actions.filter((a: ResolvedAction) => ['edit'].includes(a.name))
174
+ )
175
+
176
+ // Footer actions: actions that go in the footer (e.g., back, delete)
177
+ const footerActions = computed<ResolvedAction[]>(() =>
178
+ props.actions.filter((a: ResolvedAction) => !['edit'].includes(a.name))
179
+ )
180
+
181
+ // Get error message from fetchError
182
+ const fetchErrorMessage = computed<string | null>(() => {
183
+ if (!props.fetchError) return null
184
+ if (typeof props.fetchError === 'string') return props.fetchError
185
+ return props.fetchError.message || props.fetchError.detail || 'Failed to load entity'
186
+ })
187
+ </script>
188
+
189
+ <template>
190
+ <div class="show-page">
191
+ <!-- Nav slot for PageNav (child routes) -->
192
+ <slot name="nav" />
193
+
194
+ <PageHeader :title="title" :title-parts="titleParts" :badges="badges">
195
+ <template #actions>
196
+ <slot name="header-actions" />
197
+ <!-- Header actions from builder (e.g., edit) -->
198
+ <Button
199
+ v-for="action in headerActions"
200
+ :key="action.name"
201
+ :label="action.label"
202
+ :icon="action.icon"
203
+ :severity="(action.severity as any)"
204
+ :loading="action.isLoading"
205
+ :disabled="action.isDisabled"
206
+ @click="action.onClick"
207
+ />
208
+ </template>
209
+ </PageHeader>
210
+
211
+ <!-- Toolbar slot (between header and content) -->
212
+ <slot name="toolbar" />
213
+
214
+ <!-- Loading State -->
215
+ <template v-if="loading">
216
+ <slot name="loading">
217
+ <div class="loading-state">
218
+ <i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
219
+ </div>
220
+ </slot>
221
+ </template>
222
+
223
+ <!-- Error State (fetch error) -->
224
+ <template v-else-if="fetchError">
225
+ <slot name="error" :error="fetchError">
226
+ <Message severity="error" :closable="false" class="show-error-message">
227
+ {{ fetchErrorMessage }}
228
+ </Message>
229
+ </slot>
230
+ </template>
231
+
232
+ <!-- Content -->
233
+ <template v-else>
234
+ <!-- Card wrapper or direct content -->
235
+ <Card v-if="cardWrapper">
236
+ <template #content>
237
+ <!-- Grid layout with optional media zone -->
238
+ <div
239
+ class="show-content"
240
+ :class="{ 'show-content--with-media': slots.media }"
241
+ :style="slots.media ? { '--media-width': mediaWidth } : {}"
242
+ >
243
+ <!-- Media zone (optional) -->
244
+ <div v-if="slots.media" class="show-media">
245
+ <slot name="media" />
246
+ </div>
247
+
248
+ <!-- Fields/Groups zone -->
249
+ <div class="show-fields" :class="{ 'show-fields--horizontal': horizontalFields && !useGroupLayout }">
250
+ <!-- Group layout mode -->
251
+ <template v-if="useGroupLayout">
252
+ <slot name="groups">
253
+ <FieldGroups
254
+ :groups="groups"
255
+ :data="data"
256
+ :layout="layout"
257
+ :child-layout="childLayout"
258
+ >
259
+ <template #field="{ field, value }">
260
+ <ShowField
261
+ :field="field"
262
+ :value="value as string | number | boolean | Date | Record<string, unknown> | unknown[]"
263
+ :horizontal="horizontalFields"
264
+ :label-width="labelWidth"
265
+ />
266
+ </template>
267
+ </FieldGroups>
268
+ </slot>
269
+ </template>
270
+ <!-- Flat fields mode (default) -->
271
+ <template v-else>
272
+ <slot name="fields" />
273
+ </template>
274
+ </div>
275
+ </div>
276
+
277
+ <!-- Footer Actions -->
278
+ <template v-if="showActions && footerActions.length > 0">
279
+ <slot name="footer">
280
+ <div class="show-actions">
281
+ <Button
282
+ v-for="action in footerActions"
283
+ :key="action.name"
284
+ :label="action.label"
285
+ :icon="action.icon"
286
+ :severity="(action.severity as any)"
287
+ :loading="action.isLoading"
288
+ :disabled="action.isDisabled"
289
+ @click="action.onClick"
290
+ />
291
+ </div>
292
+ </slot>
293
+ </template>
294
+ </template>
295
+ </Card>
296
+
297
+ <template v-else>
298
+ <!-- Grid layout with optional media zone -->
299
+ <div
300
+ class="show-content"
301
+ :class="{ 'show-content--with-media': slots.media }"
302
+ :style="slots.media ? { '--media-width': mediaWidth } : {}"
303
+ >
304
+ <!-- Media zone (optional) -->
305
+ <div v-if="slots.media" class="show-media">
306
+ <slot name="media" />
307
+ </div>
308
+
309
+ <!-- Fields/Groups zone -->
310
+ <div class="show-fields" :class="{ 'show-fields--horizontal': horizontalFields && !useGroupLayout }">
311
+ <!-- Group layout mode -->
312
+ <template v-if="useGroupLayout">
313
+ <slot name="groups">
314
+ <FieldGroups
315
+ :groups="groups"
316
+ :data="data"
317
+ :layout="layout"
318
+ :child-layout="childLayout"
319
+ >
320
+ <template #field="{ field, value }">
321
+ <ShowField
322
+ :field="field"
323
+ :value="value as string | number | boolean | Date | Record<string, unknown> | unknown[]"
324
+ :horizontal="horizontalFields"
325
+ :label-width="labelWidth"
326
+ />
327
+ </template>
328
+ </FieldGroups>
329
+ </slot>
330
+ </template>
331
+ <!-- Flat fields mode (default) -->
332
+ <template v-else>
333
+ <slot name="fields" />
334
+ </template>
335
+ </div>
336
+ </div>
337
+
338
+ <!-- Footer Actions -->
339
+ <template v-if="showActions && footerActions.length > 0">
340
+ <slot name="footer">
341
+ <div class="show-actions">
342
+ <Button
343
+ v-for="action in footerActions"
344
+ :key="action.name"
345
+ :label="action.label"
346
+ :icon="action.icon"
347
+ :severity="(action.severity as any)"
348
+ :loading="action.isLoading"
349
+ :disabled="action.isDisabled"
350
+ @click="action.onClick"
351
+ />
352
+ </div>
353
+ </slot>
354
+ </template>
355
+ </template>
356
+ </template>
357
+ </div>
358
+ </template>
359
+
360
+ <style scoped>
361
+ /*
362
+ * Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
363
+ * Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
364
+ */
365
+ .show-page {
366
+ display: flex;
367
+ flex-direction: column;
368
+ gap: 1rem;
369
+ }
370
+
371
+ .loading-state {
372
+ display: flex;
373
+ justify-content: center;
374
+ padding: 3rem;
375
+ }
376
+
377
+ .show-error-message {
378
+ margin: 1rem 0;
379
+ }
380
+
381
+ /* Content grid layout */
382
+ .show-content {
383
+ display: block;
384
+ }
385
+
386
+ .show-content--with-media {
387
+ display: grid;
388
+ grid-template-columns: var(--media-width, 200px) 1fr;
389
+ gap: 2rem;
390
+ }
391
+
392
+ @media (max-width: 768px) {
393
+ .show-content--with-media {
394
+ grid-template-columns: 1fr;
395
+ }
396
+ }
397
+
398
+ /* Media zone */
399
+ .show-media {
400
+ display: flex;
401
+ flex-direction: column;
402
+ align-items: center;
403
+ text-align: center;
404
+ }
405
+
406
+ /* Fields zone */
407
+ .show-fields {
408
+ display: flex;
409
+ flex-direction: column;
410
+ gap: 1rem;
411
+ }
412
+
413
+ .show-fields--horizontal :deep(.show-field) {
414
+ flex-direction: row;
415
+ align-items: flex-start;
416
+ border-bottom: 1px solid var(--p-surface-200, #e2e8f0);
417
+ padding-bottom: 0.75rem;
418
+ }
419
+
420
+ .show-fields--horizontal :deep(.show-field:last-child) {
421
+ border-bottom: none;
422
+ }
423
+
424
+ .show-actions {
425
+ display: flex;
426
+ gap: 0.5rem;
427
+ margin-top: 1.5rem;
428
+ padding-top: 1rem;
429
+ border-top: 1px solid var(--p-surface-200, #e2e8f0);
430
+ }
431
+ </style>
@@ -0,0 +1,155 @@
1
+ /**
2
+ * qdadm - Composables exports
3
+ */
4
+
5
+ export { useBareForm, type UseBareFormOptions, type UseBareFormReturn } from './useBareForm'
6
+ export {
7
+ useBreadcrumb,
8
+ type UseBreadcrumbOptions,
9
+ type UseBreadcrumbReturn,
10
+ type BreadcrumbDisplayItem,
11
+ } from './useBreadcrumb'
12
+ export {
13
+ useSemanticBreadcrumb,
14
+ computeSemanticBreadcrumb,
15
+ type SemanticBreadcrumbItem,
16
+ } from './useSemanticBreadcrumb'
17
+ export { useDirtyState, type UseDirtyStateReturn } from './useDirtyState'
18
+ export {
19
+ useForm,
20
+ type UseFormOptions,
21
+ type UseFormReturn,
22
+ type FormAlterConfig,
23
+ } from './useForm'
24
+ export {
25
+ useEntityItemFormPage,
26
+ useEntityItemFormPage as useFormPageBuilder,
27
+ type UseEntityItemFormPageOptions,
28
+ type UseEntityItemFormPageReturn,
29
+ type FieldDefinition,
30
+ type ResolvedFieldConfig,
31
+ type ActionConfig as FormActionConfig,
32
+ type FormPageProps,
33
+ type FormPageEvents,
34
+ } from './useEntityItemFormPage'
35
+ export * from './useJsonSyntax'
36
+ export {
37
+ useListPage,
38
+ PAGE_SIZE_OPTIONS,
39
+ type UseListPageOptions,
40
+ type UseListPageReturn,
41
+ type ColumnConfig,
42
+ type FilterConfig,
43
+ type ActionConfig as ListActionConfig,
44
+ type ResolvedAction,
45
+ type HeaderActionConfig,
46
+ type ResolvedHeaderAction,
47
+ type CardConfig,
48
+ type SearchConfig,
49
+ type ListPageProps,
50
+ type ListPageEvents,
51
+ type BulkStatusActionReturn,
52
+ } from './useListPage'
53
+ export { usePageTitle, type UsePageTitleReturn, type TitleParts, type TitleInput } from './usePageTitle'
54
+ export { useApp, type UseAppReturn } from './useApp'
55
+ export { useAuth, type UseAuthReturn } from './useAuth'
56
+ export {
57
+ useEntityItemPage,
58
+ type UseEntityItemPageOptions,
59
+ type UseEntityItemPageReturn,
60
+ type ParentConfig,
61
+ } from './useEntityItemPage'
62
+ export {
63
+ useEntityItemShowPage,
64
+ useEntityItemShowPage as useShowPageBuilder,
65
+ type UseEntityItemShowPageOptions,
66
+ type UseEntityItemShowPageReturn,
67
+ type FieldDefinition as ShowFieldDefinition,
68
+ type ResolvedFieldConfig as ShowResolvedFieldConfig,
69
+ type ActionConfig as ShowActionConfig,
70
+ type LazyActionConfig as ShowLazyActionConfig,
71
+ type ShowPageProps,
72
+ type ShowPageEvents,
73
+ } from './useEntityItemShowPage'
74
+ export {
75
+ useNavContext,
76
+ type UseNavContextOptions,
77
+ type UseNavContextReturn,
78
+ type NavChainSegment,
79
+ type NavChainType,
80
+ type BreadcrumbItem,
81
+ type NavLinkItem,
82
+ } from './useNavContext'
83
+ export { useNavigation, type UseNavigationReturn, type NavItem, type NavSection } from './useNavigation'
84
+ export {
85
+ useUnsavedChangesGuard,
86
+ type UseUnsavedChangesGuardOptions,
87
+ type UseUnsavedChangesGuardReturn,
88
+ type GuardDialogState,
89
+ } from './useUnsavedChangesGuard'
90
+ export { useGuardDialog, type GuardDialog } from './useGuardStore'
91
+ export { useSignals } from './useSignals'
92
+ export {
93
+ useZoneRegistry,
94
+ type UseZoneRegistryReturn,
95
+ type BlockConfig,
96
+ type ZoneDefinition,
97
+ type ZoneInfo,
98
+ type ZoneRegistry,
99
+ } from './useZoneRegistry'
100
+ export { useHooks } from './useHooks'
101
+ export {
102
+ useLayoutResolver,
103
+ createLayoutComponents,
104
+ layoutMeta,
105
+ LAYOUT_TYPES,
106
+ type UseLayoutResolverOptions,
107
+ type UseLayoutResolverReturn,
108
+ type LayoutType,
109
+ type LayoutComponentsMap,
110
+ type LayoutComponentsInput,
111
+ type LayoutMeta,
112
+ } from './useLayoutResolver'
113
+ export { useSSEBridge, type UseSSEBridgeReturn } from './useSSEBridge'
114
+ export {
115
+ useDeferred,
116
+ useDeferredValue,
117
+ DEFERRED_INJECTION_KEY,
118
+ type UseDeferredValueReturn,
119
+ } from './useDeferred'
120
+ export {
121
+ useInfoBanner,
122
+ provideBannerStore,
123
+ createBannerStore,
124
+ type Banner,
125
+ type BannerOptions,
126
+ type BannerSeverity,
127
+ type BannerStore,
128
+ } from './useInfoBanner'
129
+ export {
130
+ useUserImpersonator,
131
+ type UseUserImpersonatorOptions,
132
+ type UseUserImpersonatorReturn,
133
+ type UserRecord,
134
+ } from './useUserImpersonator'
135
+ export { useCurrentEntity, type UseCurrentEntityReturn } from './useCurrentEntity'
136
+ export {
137
+ useOptionsLookup,
138
+ type UseOptionsLookupConfig,
139
+ type UseOptionsLookupReturn,
140
+ type OptionsLookupItem,
141
+ } from './useOptionsLookup'
142
+ export {
143
+ useFieldManager,
144
+ snakeCaseToTitle,
145
+ type BaseFieldDefinition,
146
+ type ResolvedFieldConfig as FieldManagerFieldConfig,
147
+ type FieldGroup,
148
+ type GroupDefinition,
149
+ type GroupOptions,
150
+ type GenerateFieldsOptions as FieldManagerGenerateOptions,
151
+ type AddFieldOptions as FieldManagerAddOptions,
152
+ type MoveFieldPosition,
153
+ type UseFieldManagerOptions,
154
+ type UseFieldManagerReturn,
155
+ } from './useFieldManager'
@@ -0,0 +1,67 @@
1
+ /**
2
+ * useApp - Access app branding and configuration
3
+ *
4
+ * Provides access to app config set via createQdadm bootstrap.
5
+ *
6
+ * Usage:
7
+ * const { name, version, logo } = useApp()
8
+ */
9
+
10
+ import { inject, computed, type ComputedRef } from 'vue'
11
+
12
+ /**
13
+ * App configuration interface
14
+ */
15
+ export interface AppConfig {
16
+ name?: string
17
+ shortName?: string
18
+ logo?: string | null
19
+ logoSmall?: string | null
20
+ version?: string | (() => string) | null
21
+ theme?: Record<string, unknown>
22
+ }
23
+
24
+ /**
25
+ * Return type for useApp
26
+ */
27
+ export interface UseAppReturn {
28
+ name: string
29
+ shortName: string
30
+ logo: string | null | undefined
31
+ logoSmall: string | null | undefined
32
+ version: ComputedRef<string | null | undefined>
33
+ theme: Record<string, unknown>
34
+ }
35
+
36
+ export function useApp(): UseAppReturn {
37
+ const appConfig = inject<AppConfig | null>('qdadmApp', null)
38
+
39
+ if (!appConfig) {
40
+ console.warn('[qdadm] qdadmApp not provided. Using defaults.')
41
+ return {
42
+ name: 'Admin',
43
+ shortName: 'Admin',
44
+ logo: null,
45
+ logoSmall: null,
46
+ version: computed(() => null),
47
+ theme: {},
48
+ }
49
+ }
50
+
51
+ // Version can be string or callback
52
+ const version = computed(() => {
53
+ if (typeof appConfig.version === 'function') {
54
+ return appConfig.version()
55
+ }
56
+ return appConfig.version ?? null
57
+ })
58
+
59
+ return {
60
+ name: appConfig.name || 'Admin',
61
+ shortName: appConfig.shortName || appConfig.name || 'Admin',
62
+ logo: appConfig.logo,
63
+ logoSmall: appConfig.logoSmall,
64
+ version,
65
+ theme: appConfig.theme || {},
66
+ }
67
+ }