@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,343 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ShowDisplay - Auto-renders the appropriate display widget based on field config
4
+ *
5
+ * Takes a field config object and renders the matching display component.
6
+ * Supports hint override but field.type takes precedence if specified.
7
+ *
8
+ * Usage:
9
+ * <ShowDisplay :field="f" :value="data[f.name]" />
10
+ *
11
+ * Display types:
12
+ * - text: simple text
13
+ * - number: formatted number
14
+ * - boolean: Yes/No or icon
15
+ * - date: formatted date
16
+ * - datetime: formatted datetime
17
+ * - select: label from options
18
+ * - email: mailto link
19
+ * - password: masked
20
+ * - textarea: multi-line text
21
+ * - reference: link to entity (requires referenceRoute)
22
+ * - image: image preview
23
+ * - url: clickable link
24
+ * - json: formatted JSON
25
+ * - currency: formatted currency
26
+ * - badge: styled tag
27
+ */
28
+ import { computed, type PropType } from 'vue'
29
+ import { RouterLink, type RouteLocationRaw } from 'vue-router'
30
+ import Tag from 'primevue/tag'
31
+
32
+ type DisplayType =
33
+ | 'text'
34
+ | 'email'
35
+ | 'password'
36
+ | 'number'
37
+ | 'textarea'
38
+ | 'select'
39
+ | 'boolean'
40
+ | 'date'
41
+ | 'datetime'
42
+ | 'reference'
43
+ | 'image'
44
+ | 'url'
45
+ | 'json'
46
+ | 'currency'
47
+ | 'badge'
48
+
49
+ type DisplayValue = string | number | boolean | Date | null | undefined | unknown
50
+
51
+ interface SelectOption {
52
+ label?: string
53
+ value?: string | number
54
+ [key: string]: unknown
55
+ }
56
+
57
+ interface FieldConfig {
58
+ name: string
59
+ type?: DisplayType
60
+ // Display options
61
+ dateFormat?: string
62
+ currencyCode?: string
63
+ locale?: string
64
+ booleanLabels?: { true: string; false: string }
65
+ // Reference options
66
+ reference?: string
67
+ referenceRoute?: string | ((value: unknown) => { name: string; params: Record<string, unknown> })
68
+ referenceLabel?: string | ((value: unknown, option?: unknown) => string)
69
+ // Select options (for label lookup)
70
+ options?: SelectOption[]
71
+ optionLabel?: string
72
+ optionValue?: string
73
+ // Badge options
74
+ severity?: string | { severity: string; icon?: string; label?: string } | ((value: unknown) => string | { severity: string; icon?: string; label?: string })
75
+ // Image options
76
+ imageWidth?: string
77
+ imageHeight?: string
78
+ // Custom render
79
+ render?: (value: unknown) => string
80
+ }
81
+
82
+ const props = defineProps({
83
+ field: { type: Object as PropType<FieldConfig>, required: true },
84
+ value: { type: [String, Number, Boolean, Date, Object, Array] as PropType<DisplayValue>, default: null },
85
+ hint: { type: String as PropType<DisplayType | null>, default: null }
86
+ })
87
+
88
+ // Resolve display type: field.type > hint > 'text'
89
+ const displayType = computed<DisplayType>(() => (props.field?.type as DisplayType) || props.hint || 'text')
90
+
91
+ // Check if value is empty (render() overrides: the function handles its own empty state)
92
+ const isEmpty = computed(() => {
93
+ if (props.field.render) return false
94
+ return props.value === null || props.value === undefined || props.value === ''
95
+ })
96
+
97
+ // Format text value
98
+ const textValue = computed(() => {
99
+ if (isEmpty.value) return '-'
100
+ if (props.field.render) return props.field.render(props.value)
101
+ return String(props.value)
102
+ })
103
+
104
+ // Format number value
105
+ const numberValue = computed(() => {
106
+ if (isEmpty.value) return '-'
107
+ const num = Number(props.value)
108
+ if (isNaN(num)) return String(props.value)
109
+ const locale = props.field.locale || 'en-US'
110
+ return new Intl.NumberFormat(locale).format(num)
111
+ })
112
+
113
+ // Format currency value
114
+ const currencyValue = computed(() => {
115
+ if (isEmpty.value) return '-'
116
+ const num = Number(props.value)
117
+ if (isNaN(num)) return String(props.value)
118
+ const locale = props.field.locale || 'en-US'
119
+ const currency = props.field.currencyCode || 'USD'
120
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(num)
121
+ })
122
+
123
+ // Format boolean value
124
+ const booleanValue = computed(() => {
125
+ if (isEmpty.value) return '-'
126
+ const labels = props.field.booleanLabels || { true: 'Yes', false: 'No' }
127
+ return props.value ? labels.true : labels.false
128
+ })
129
+
130
+ const booleanIcon = computed(() => {
131
+ if (isEmpty.value) return 'pi-minus'
132
+ return props.value ? 'pi-check' : 'pi-times'
133
+ })
134
+
135
+ const booleanIconClass = computed(() => {
136
+ if (isEmpty.value) return 'text-muted'
137
+ return props.value ? 'text-success' : 'text-danger'
138
+ })
139
+
140
+ // Format date value
141
+ const dateValue = computed(() => {
142
+ if (isEmpty.value) return '-'
143
+ const v = props.value as unknown
144
+ const date = (typeof v === 'object' && v instanceof Date) ? v : new Date(String(v))
145
+ if (isNaN(date.getTime())) return String(props.value)
146
+ const locale = props.field.locale || 'en-US'
147
+ const format = props.field.dateFormat || 'short'
148
+ return new Intl.DateTimeFormat(locale, { dateStyle: format as 'short' | 'medium' | 'long' | 'full' }).format(date)
149
+ })
150
+
151
+ // Format datetime value
152
+ const datetimeValue = computed(() => {
153
+ if (isEmpty.value) return '-'
154
+ const v = props.value as unknown
155
+ const date = (typeof v === 'object' && v instanceof Date) ? v : new Date(String(v))
156
+ if (isNaN(date.getTime())) return String(props.value)
157
+ const locale = props.field.locale || 'en-US'
158
+ return new Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short' }).format(date)
159
+ })
160
+
161
+ // Format select value (lookup label from options)
162
+ const selectValue = computed(() => {
163
+ if (isEmpty.value) return '-'
164
+ const options = props.field.options || []
165
+ const optionValue = props.field.optionValue || 'value'
166
+ const optionLabel = props.field.optionLabel || 'label'
167
+
168
+ const option = options.find((opt) => opt[optionValue] === props.value)
169
+ if (option) {
170
+ return option[optionLabel] as string
171
+ }
172
+ return String(props.value)
173
+ })
174
+
175
+ // Reference route
176
+ const referenceRoute = computed<RouteLocationRaw | null>(() => {
177
+ if (!props.field.referenceRoute || isEmpty.value) return null
178
+ if (typeof props.field.referenceRoute === 'function') {
179
+ return props.field.referenceRoute(props.value) as RouteLocationRaw
180
+ }
181
+ return { name: props.field.referenceRoute, params: { id: String(props.value) } }
182
+ })
183
+
184
+ // Reference label
185
+ const referenceLabel = computed(() => {
186
+ if (isEmpty.value) return '-'
187
+ if (typeof props.field.referenceLabel === 'function') {
188
+ return props.field.referenceLabel(props.value)
189
+ }
190
+ if (props.field.referenceLabel) {
191
+ return props.field.referenceLabel
192
+ }
193
+ return String(props.value)
194
+ })
195
+
196
+ // Badge value (supports render())
197
+ const badgeValue = computed(() => {
198
+ if (props.field.render) return props.field.render(props.value)
199
+ if (props.value === null || props.value === undefined) return ''
200
+ return String(props.value)
201
+ })
202
+
203
+ // Badge descriptor (normalized to { severity, icon? })
204
+ const badgeDescriptor = computed(() => {
205
+ const raw = typeof props.field.severity === 'function'
206
+ ? props.field.severity(props.value)
207
+ : props.field.severity
208
+ if (!raw) return { severity: 'info' }
209
+ return typeof raw === 'string' ? { severity: raw } : raw
210
+ })
211
+
212
+ const badgeSeverity = computed(() => badgeDescriptor.value.severity)
213
+ const badgeIcon = computed(() => badgeDescriptor.value.icon)
214
+
215
+ // JSON formatted
216
+ const jsonValue = computed(() => {
217
+ if (isEmpty.value) return '-'
218
+ try {
219
+ return JSON.stringify(props.value, null, 2)
220
+ } catch {
221
+ return String(props.value)
222
+ }
223
+ })
224
+
225
+ // Image dimensions
226
+ const imageStyle = computed(() => ({
227
+ maxWidth: props.field.imageWidth || '200px',
228
+ maxHeight: props.field.imageHeight || '150px'
229
+ }))
230
+ </script>
231
+
232
+ <template>
233
+ <!-- Empty value -->
234
+ <span v-if="isEmpty" class="show-display show-display--empty">-</span>
235
+
236
+ <!-- Text -->
237
+ <span v-else-if="displayType === 'text'" class="show-display show-display--text">
238
+ {{ textValue }}
239
+ </span>
240
+
241
+ <!-- Email -->
242
+ <a
243
+ v-else-if="displayType === 'email'"
244
+ :href="`mailto:${value}`"
245
+ class="show-display show-display--email"
246
+ >
247
+ {{ value }}
248
+ </a>
249
+
250
+ <!-- Password (masked) -->
251
+ <span v-else-if="displayType === 'password'" class="show-display show-display--password">
252
+ ••••••••
253
+ </span>
254
+
255
+ <!-- Number -->
256
+ <span v-else-if="displayType === 'number'" class="show-display show-display--number">
257
+ {{ numberValue }}
258
+ </span>
259
+
260
+ <!-- Currency -->
261
+ <span v-else-if="displayType === 'currency'" class="show-display show-display--currency">
262
+ {{ currencyValue }}
263
+ </span>
264
+
265
+ <!-- Boolean (icon) -->
266
+ <span v-else-if="displayType === 'boolean'" class="show-display show-display--boolean" :class="booleanIconClass">
267
+ <i :class="['pi', booleanIcon]"></i>
268
+ <span class="boolean-label">{{ booleanValue }}</span>
269
+ </span>
270
+
271
+ <!-- Date -->
272
+ <span v-else-if="displayType === 'date'" class="show-display show-display--date">
273
+ {{ dateValue }}
274
+ </span>
275
+
276
+ <!-- Datetime -->
277
+ <span v-else-if="displayType === 'datetime'" class="show-display show-display--datetime">
278
+ {{ datetimeValue }}
279
+ </span>
280
+
281
+ <!-- Select (label lookup) -->
282
+ <span v-else-if="displayType === 'select'" class="show-display show-display--select">
283
+ {{ selectValue }}
284
+ </span>
285
+
286
+ <!-- Textarea (multi-line) -->
287
+ <div v-else-if="displayType === 'textarea'" class="show-display show-display--textarea">
288
+ {{ textValue }}
289
+ </div>
290
+
291
+ <!-- Reference (link) -->
292
+ <RouterLink
293
+ v-else-if="displayType === 'reference' && referenceRoute"
294
+ :to="referenceRoute"
295
+ class="show-display show-display--reference"
296
+ >
297
+ {{ referenceLabel }}
298
+ </RouterLink>
299
+ <span v-else-if="displayType === 'reference'" class="show-display show-display--reference">
300
+ {{ referenceLabel }}
301
+ </span>
302
+
303
+ <!-- URL -->
304
+ <a
305
+ v-else-if="displayType === 'url'"
306
+ :href="String(value)"
307
+ target="_blank"
308
+ rel="noopener noreferrer"
309
+ class="show-display show-display--url"
310
+ >
311
+ {{ value }}
312
+ <i class="pi pi-external-link" style="font-size: 0.75em; margin-left: 0.25em"></i>
313
+ </a>
314
+
315
+ <!-- Image -->
316
+ <div v-else-if="displayType === 'image'" class="show-display show-display--image">
317
+ <img :src="String(value)" :alt="field.name" :style="imageStyle" />
318
+ </div>
319
+
320
+ <!-- JSON -->
321
+ <pre v-else-if="displayType === 'json'" class="show-display show-display--json">{{ jsonValue }}</pre>
322
+
323
+ <!-- Badge (with optional icon) -->
324
+ <span
325
+ v-else-if="displayType === 'badge' && badgeIcon"
326
+ class="show-display show-display--badge p-tag p-component"
327
+ :class="`p-tag-${badgeSeverity}`"
328
+ >
329
+ <i :class="badgeIcon" />
330
+ <span class="p-tag-label">{{ badgeValue }}</span>
331
+ </span>
332
+ <Tag
333
+ v-else-if="displayType === 'badge'"
334
+ :value="badgeValue"
335
+ :severity="badgeSeverity"
336
+ class="show-display show-display--badge"
337
+ />
338
+
339
+ <!-- Fallback to text -->
340
+ <span v-else class="show-display show-display--text">
341
+ {{ textValue }}
342
+ </span>
343
+ </template>
@@ -0,0 +1,67 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ShowField - Display field wrapper with label
4
+ *
5
+ * Provides consistent styling for field display in ShowPage.
6
+ * Mirrors FormField but for read-only display.
7
+ *
8
+ * Usage:
9
+ * <ShowField :name="f.name" :label="f.label">
10
+ * <ShowDisplay :field="f" :value="data[f.name]" />
11
+ * </ShowField>
12
+ *
13
+ * Or with auto-display:
14
+ * <ShowField :field="f" :value="data[f.name]" />
15
+ */
16
+ import { computed, type PropType } from 'vue'
17
+ import ShowDisplay from './ShowDisplay.vue'
18
+
19
+ interface FieldConfig {
20
+ name: string
21
+ type?: string
22
+ label?: string
23
+ [key: string]: unknown
24
+ }
25
+
26
+ const props = defineProps({
27
+ // Field config (for auto-display mode)
28
+ field: { type: Object as PropType<FieldConfig | null>, default: null },
29
+ // Value (for auto-display mode)
30
+ value: { type: [String, Number, Boolean, Date, Object, Array], default: null },
31
+ // Manual props (override field config)
32
+ name: { type: String, default: '' },
33
+ label: { type: String, default: '' },
34
+ // Layout options
35
+ horizontal: { type: Boolean, default: false },
36
+ labelWidth: { type: String, default: '120px' }
37
+ })
38
+
39
+ // Resolved name
40
+ const fieldName = computed(() => props.name || props.field?.name || '')
41
+
42
+ // Resolved label
43
+ const fieldLabel = computed(() => props.label || props.field?.label || fieldName.value)
44
+
45
+ // Auto-display mode (when field + value provided, no slot content)
46
+ const autoDisplay = computed(() => props.field !== null)
47
+ </script>
48
+
49
+ <template>
50
+ <div
51
+ class="show-field"
52
+ :class="{
53
+ 'show-field--horizontal': horizontal
54
+ }"
55
+ >
56
+ <label class="show-field__label" :style="horizontal ? { minWidth: labelWidth } : {}">
57
+ {{ fieldLabel }}
58
+ </label>
59
+ <div class="show-field__value">
60
+ <!-- Slot for custom content -->
61
+ <slot>
62
+ <!-- Auto-display when field is provided -->
63
+ <ShowDisplay v-if="autoDisplay" :field="(field as any)" :value="(value as any)" />
64
+ </slot>
65
+ </div>
66
+ </div>
67
+ </template>