@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,331 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * MultiStepDialog - Reusable multi-step form dialog
4
+ *
5
+ * Features:
6
+ * - Step indicator with labels
7
+ * - Automatic navigation buttons (Cancel, Back, Next, Submit)
8
+ * - Per-step validation
9
+ * - Linear progression mode
10
+ * - Loading state for submit
11
+ * - Customizable button labels
12
+ *
13
+ * Usage:
14
+ * <MultiStepDialog
15
+ * v-model:visible="showDialog"
16
+ * v-model:step="currentStep"
17
+ * title="Create Item"
18
+ * :steps="[
19
+ * { label: 'Step 1', valid: !!field1 },
20
+ * { label: 'Step 2', valid: canSubmit }
21
+ * ]"
22
+ * :loading="submitting"
23
+ * @submit="handleSubmit"
24
+ * @cancel="handleCancel"
25
+ * >
26
+ * <template #step-1>
27
+ * <!-- Step 1 content -->
28
+ * </template>
29
+ * <template #step-2>
30
+ * <!-- Step 2 content -->
31
+ * </template>
32
+ * </MultiStepDialog>
33
+ *
34
+ * Props:
35
+ * - visible: boolean (v-model) - controls dialog visibility
36
+ * - step: number (v-model) - current step (1-indexed)
37
+ * - title: string - dialog title
38
+ * - steps: Array<{ label: string, valid?: boolean }> - step definitions
39
+ * - loading: boolean - shows loading state on submit button
40
+ * - width: string - dialog width (default: '800px')
41
+ * - linear: boolean - require validation before next step (default: true)
42
+ * - submitLabel: string - label for submit button (default: 'Submit')
43
+ * - cancelLabel: string - label for cancel button (default: 'Cancel')
44
+ * - nextLabel: string - label for next button (default: 'Next')
45
+ * - backLabel: string - label for back button (default: 'Back')
46
+ * - showBackOnFirst: boolean - show back button on first step (default: false)
47
+ * - hideStepIndicator: boolean - hide the step indicator (default: false)
48
+ *
49
+ * Events:
50
+ * - submit: emitted when submit button is clicked on last step
51
+ * - cancel: emitted when cancel button is clicked
52
+ * - step-change: emitted when step changes, payload: { from, to }
53
+ *
54
+ * Slots:
55
+ * - step-{n}: content for step n (1-indexed)
56
+ * - step-{n}-header: optional header content for step n (shown above step content)
57
+ * - actions: override action buttons (receives { step, isFirst, isLast, canProceed, goNext, goPrev, submit, cancel })
58
+ */
59
+ import { computed, watch, type PropType } from 'vue'
60
+ import Dialog from 'primevue/dialog'
61
+ import Button from 'primevue/button'
62
+ import Stepper from 'primevue/stepper'
63
+ import StepList from 'primevue/steplist'
64
+ import Step from 'primevue/step'
65
+ import StepPanels from 'primevue/steppanels'
66
+ import StepPanel from 'primevue/steppanel'
67
+
68
+ export interface StepDefinition {
69
+ label: string
70
+ valid?: boolean
71
+ }
72
+
73
+ export interface StepChangeEvent {
74
+ from: number
75
+ to: number
76
+ }
77
+
78
+ export interface SlotActions {
79
+ step: number
80
+ isFirst: boolean
81
+ isLast: boolean
82
+ canProceed: boolean
83
+ canSubmit: boolean
84
+ goNext: () => void
85
+ goPrev: () => void
86
+ goToStep: (stepNumber: number) => void
87
+ submit: () => void
88
+ cancel: () => void
89
+ }
90
+
91
+ const props = defineProps({
92
+ visible: {
93
+ type: Boolean,
94
+ default: false
95
+ },
96
+ step: {
97
+ type: Number,
98
+ default: 1
99
+ },
100
+ title: {
101
+ type: String,
102
+ default: ''
103
+ },
104
+ steps: {
105
+ type: Array as PropType<StepDefinition[]>,
106
+ required: true,
107
+ validator: (v: StepDefinition[]) => v.every(s => typeof s.label === 'string')
108
+ },
109
+ loading: {
110
+ type: Boolean,
111
+ default: false
112
+ },
113
+ width: {
114
+ type: String,
115
+ default: '800px'
116
+ },
117
+ linear: {
118
+ type: Boolean,
119
+ default: true
120
+ },
121
+ submitLabel: {
122
+ type: String,
123
+ default: 'Submit'
124
+ },
125
+ cancelLabel: {
126
+ type: String,
127
+ default: 'Cancel'
128
+ },
129
+ nextLabel: {
130
+ type: String,
131
+ default: 'Next'
132
+ },
133
+ backLabel: {
134
+ type: String,
135
+ default: 'Back'
136
+ },
137
+ showBackOnFirst: {
138
+ type: Boolean,
139
+ default: false
140
+ },
141
+ hideStepIndicator: {
142
+ type: Boolean,
143
+ default: false
144
+ }
145
+ })
146
+
147
+ const emit = defineEmits<{
148
+ 'update:visible': [value: boolean]
149
+ 'update:step': [value: number]
150
+ 'submit': []
151
+ 'cancel': []
152
+ 'step-change': [event: StepChangeEvent]
153
+ }>()
154
+
155
+ // Current step (1-indexed)
156
+ const currentStep = computed<number>({
157
+ get: (): number => props.step,
158
+ set: (v: number): void => emit('update:step', v)
159
+ })
160
+
161
+ // Step helpers
162
+ const isFirstStep = computed<boolean>(() => currentStep.value === 1)
163
+ const isLastStep = computed<boolean>(() => currentStep.value === props.steps.length)
164
+
165
+ // Current step definition
166
+ const currentStepDef = computed<StepDefinition>(() => props.steps[currentStep.value - 1] || { label: '' })
167
+
168
+ // Can proceed to next step (validation)
169
+ const canProceed = computed<boolean>(() => {
170
+ if (!props.linear) return true
171
+ return currentStepDef.value.valid !== false
172
+ })
173
+
174
+ // Can submit (last step validation)
175
+ const canSubmit = computed<boolean>(() => {
176
+ return currentStepDef.value.valid !== false
177
+ })
178
+
179
+ // Navigation
180
+ function goNext(): void {
181
+ if (!isLastStep.value && canProceed.value) {
182
+ const from = currentStep.value
183
+ const to = currentStep.value + 1
184
+ currentStep.value = to
185
+ emit('step-change', { from, to })
186
+ }
187
+ }
188
+
189
+ function goPrev(): void {
190
+ if (!isFirstStep.value) {
191
+ const from = currentStep.value
192
+ const to = currentStep.value - 1
193
+ currentStep.value = to
194
+ emit('step-change', { from, to })
195
+ }
196
+ }
197
+
198
+ function goToStep(stepNumber: number): void {
199
+ if (stepNumber >= 1 && stepNumber <= props.steps.length) {
200
+ // In linear mode, can only go back or to current+1 if valid
201
+ if (props.linear && stepNumber > currentStep.value) {
202
+ if (stepNumber > currentStep.value + 1 || !canProceed.value) {
203
+ return
204
+ }
205
+ }
206
+ const from = currentStep.value
207
+ currentStep.value = stepNumber
208
+ emit('step-change', { from, to: stepNumber })
209
+ }
210
+ }
211
+
212
+ function submit(): void {
213
+ if (canSubmit.value) {
214
+ emit('submit')
215
+ }
216
+ }
217
+
218
+ function cancel(): void {
219
+ emit('cancel')
220
+ emit('update:visible', false)
221
+ }
222
+
223
+ // Reset to first step when dialog opens
224
+ watch(() => props.visible, (newVal: boolean, oldVal: boolean) => {
225
+ if (newVal && !oldVal) {
226
+ // Don't reset if step is explicitly set
227
+ // currentStep.value = 1
228
+ }
229
+ })
230
+
231
+ // Expose for slot actions
232
+ const slotActions = computed<SlotActions>(() => ({
233
+ step: currentStep.value,
234
+ isFirst: isFirstStep.value,
235
+ isLast: isLastStep.value,
236
+ canProceed: canProceed.value,
237
+ canSubmit: canSubmit.value,
238
+ goNext,
239
+ goPrev,
240
+ goToStep,
241
+ submit,
242
+ cancel
243
+ }))
244
+ </script>
245
+
246
+ <template>
247
+ <Dialog
248
+ :visible="props.visible"
249
+ @update:visible="emit('update:visible', $event)"
250
+ :header="title"
251
+ :style="{ width: width }"
252
+ :modal="true"
253
+ :closable="!loading"
254
+ :closeOnEscape="!loading"
255
+ >
256
+ <div class="multi-step-dialog">
257
+ <Stepper v-model:value="currentStep" :linear="linear">
258
+ <!-- Step indicator -->
259
+ <StepList v-if="!hideStepIndicator">
260
+ <Step
261
+ v-for="(stepDef, idx) in steps"
262
+ :key="idx"
263
+ :value="idx + 1"
264
+ >
265
+ {{ stepDef.label }}
266
+ </Step>
267
+ </StepList>
268
+
269
+ <!-- Step panels -->
270
+ <StepPanels>
271
+ <StepPanel
272
+ v-for="(stepDef, idx) in steps"
273
+ :key="idx"
274
+ :value="idx + 1"
275
+ >
276
+ <div class="step-content">
277
+ <!-- Optional step header slot -->
278
+ <slot :name="`step-${idx + 1}-header`" v-bind="slotActions" />
279
+
280
+ <!-- Step content slot -->
281
+ <slot :name="`step-${idx + 1}`" v-bind="slotActions" />
282
+
283
+ <!-- Action buttons -->
284
+ <div class="step-actions">
285
+ <slot name="actions" v-bind="slotActions">
286
+ <!-- Back button -->
287
+ <Button
288
+ v-if="!isFirstStep || showBackOnFirst"
289
+ :label="backLabel"
290
+ icon="pi pi-arrow-left"
291
+ severity="secondary"
292
+ @click="goPrev"
293
+ :disabled="loading || isFirstStep"
294
+ />
295
+
296
+ <!-- Cancel button -->
297
+ <Button
298
+ :label="cancelLabel"
299
+ severity="secondary"
300
+ @click="cancel"
301
+ :disabled="loading"
302
+ />
303
+
304
+ <!-- Next button (not on last step) -->
305
+ <Button
306
+ v-if="!isLastStep"
307
+ :label="nextLabel"
308
+ icon="pi pi-arrow-right"
309
+ iconPos="right"
310
+ @click="goNext"
311
+ :disabled="!canProceed"
312
+ />
313
+
314
+ <!-- Submit button (only on last step) -->
315
+ <Button
316
+ v-if="isLastStep"
317
+ :label="submitLabel"
318
+ icon="pi pi-check"
319
+ @click="submit"
320
+ :loading="loading"
321
+ :disabled="!canSubmit"
322
+ />
323
+ </slot>
324
+ </div>
325
+ </div>
326
+ </StepPanel>
327
+ </StepPanels>
328
+ </Stepper>
329
+ </div>
330
+ </Dialog>
331
+ </template>
@@ -0,0 +1,122 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * SimpleDialog - Wrapper for common dialog patterns
4
+ *
5
+ * Simplifies the most common dialog use case:
6
+ * - Modal with title
7
+ * - Content slot
8
+ * - Cancel/Confirm footer buttons
9
+ *
10
+ * Usage:
11
+ * <SimpleDialog
12
+ * v-model:visible="showDialog"
13
+ * title="Confirm Action"
14
+ * :loading="saving"
15
+ * @confirm="onSave"
16
+ * >
17
+ * <p>Are you sure?</p>
18
+ * </SimpleDialog>
19
+ *
20
+ * For read-only dialogs (no confirm button):
21
+ * <SimpleDialog v-model:visible="showDialog" title="View" :showConfirm="false">
22
+ * <p>Content here</p>
23
+ * </SimpleDialog>
24
+ *
25
+ * For dialogs with extra action buttons:
26
+ * <SimpleDialog v-model:visible="showDialog" title="View Key" :showConfirm="false">
27
+ * <p>{{ key }}</p>
28
+ * <template #actions>
29
+ * <Button label="Copy" icon="pi pi-copy" @click="copyKey" />
30
+ * </template>
31
+ * </SimpleDialog>
32
+ */
33
+ import { computed } from 'vue'
34
+ import Dialog from 'primevue/dialog'
35
+ import Button from 'primevue/button'
36
+
37
+ interface Props {
38
+ visible?: boolean
39
+ title: string
40
+ width?: string
41
+ // Footer buttons
42
+ showCancel?: boolean
43
+ showConfirm?: boolean
44
+ cancelLabel?: string
45
+ confirmLabel?: string
46
+ confirmIcon?: string
47
+ confirmSeverity?: string
48
+ confirmDisabled?: boolean
49
+ loading?: boolean
50
+ // Dialog behavior
51
+ closable?: boolean
52
+ dismissableMask?: boolean
53
+ }
54
+
55
+ const props = withDefaults(defineProps<Props>(), {
56
+ visible: false,
57
+ width: '500px',
58
+ showCancel: true,
59
+ showConfirm: true,
60
+ cancelLabel: 'Cancel',
61
+ confirmLabel: 'Confirm',
62
+ confirmIcon: undefined,
63
+ confirmSeverity: undefined,
64
+ confirmDisabled: false,
65
+ loading: false,
66
+ closable: true,
67
+ dismissableMask: false
68
+ })
69
+
70
+ const emit = defineEmits<{
71
+ 'update:visible': [value: boolean]
72
+ 'confirm': []
73
+ 'cancel': []
74
+ }>()
75
+
76
+ function onCancel(): void {
77
+ emit('cancel')
78
+ emit('update:visible', false)
79
+ }
80
+
81
+ function onConfirm(): void {
82
+ emit('confirm')
83
+ }
84
+
85
+ // Allow closing via escape/mask only when not loading
86
+ const canClose = computed((): boolean => props.closable && !props.loading)
87
+ </script>
88
+
89
+ <template>
90
+ <Dialog
91
+ :visible="visible"
92
+ :header="title"
93
+ :modal="true"
94
+ :style="{ width }"
95
+ :closable="canClose"
96
+ :closeOnEscape="canClose"
97
+ :dismissableMask="dismissableMask && canClose"
98
+ @update:visible="$emit('update:visible', $event)"
99
+ >
100
+ <slot />
101
+
102
+ <template #footer v-if="showCancel || showConfirm || $slots.actions">
103
+ <slot name="actions" />
104
+ <Button
105
+ v-if="showCancel"
106
+ :label="cancelLabel"
107
+ severity="secondary"
108
+ @click="onCancel"
109
+ :disabled="loading"
110
+ />
111
+ <Button
112
+ v-if="showConfirm"
113
+ :label="confirmLabel"
114
+ :icon="confirmIcon"
115
+ :severity="confirmSeverity"
116
+ :loading="loading"
117
+ :disabled="confirmDisabled"
118
+ @click="onConfirm"
119
+ />
120
+ </template>
121
+ </Dialog>
122
+ </template>
@@ -0,0 +1,80 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * UnsavedChangesDialog - Confirm leaving with unsaved changes
4
+ *
5
+ * Uses SimpleDialog with custom actions:
6
+ * - Save & Leave (optional): saves then navigates
7
+ * - Leave: discards changes and navigates
8
+ * - Stay: cancels navigation
9
+ */
10
+ import SimpleDialog from './SimpleDialog.vue'
11
+ import Button from 'primevue/button'
12
+
13
+ interface Props {
14
+ visible?: boolean
15
+ saving?: boolean
16
+ message?: string
17
+ hasOnSave?: boolean
18
+ }
19
+
20
+ withDefaults(defineProps<Props>(), {
21
+ visible: false,
22
+ saving: false,
23
+ message: 'You have unsaved changes that will be lost.',
24
+ hasOnSave: false
25
+ })
26
+
27
+ const emit = defineEmits<{
28
+ 'update:visible': [value: boolean]
29
+ saveAndLeave: []
30
+ leave: []
31
+ stay: []
32
+ }>()
33
+
34
+ function onStay(): void {
35
+ emit('stay')
36
+ emit('update:visible', false)
37
+ }
38
+
39
+ function onLeave(): void {
40
+ emit('leave')
41
+ }
42
+
43
+ function onSaveAndLeave(): void {
44
+ emit('saveAndLeave')
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <SimpleDialog
50
+ :visible="visible"
51
+ title="Unsaved Changes"
52
+ width="400px"
53
+ :closable="false"
54
+ :showCancel="true"
55
+ :showConfirm="true"
56
+ cancelLabel="Stay"
57
+ confirmLabel="Leave"
58
+ confirmSeverity="danger"
59
+ :loading="saving"
60
+ @cancel="onStay"
61
+ @confirm="onLeave"
62
+ @update:visible="$emit('update:visible', $event)"
63
+ >
64
+ <div class="dialog-content">
65
+ <i class="pi pi-exclamation-triangle dialog-icon"></i>
66
+ <p>{{ message }}</p>
67
+ </div>
68
+
69
+ <template #actions>
70
+ <Button
71
+ v-if="hasOnSave"
72
+ label="Save & Leave"
73
+ icon="pi pi-save"
74
+ severity="success"
75
+ :loading="saving"
76
+ @click="onSaveAndLeave"
77
+ />
78
+ </template>
79
+ </SimpleDialog>
80
+ </template>
@@ -0,0 +1,65 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * BannerZone - Renders programmatic banners from useInfoBanner
4
+ *
5
+ * Place this component where you want banners to appear (typically
6
+ * at the top of page content).
7
+ *
8
+ * @example
9
+ * <template>
10
+ * <div class="page-content">
11
+ * <BannerZone />
12
+ * <!-- rest of page content -->
13
+ * </div>
14
+ * </template>
15
+ */
16
+ import { useInfoBanner } from '../../composables/useInfoBanner'
17
+ import InfoBanner from '../InfoBanner.vue'
18
+
19
+ const { banners, hideBanner } = useInfoBanner()
20
+ </script>
21
+
22
+ <template>
23
+ <TransitionGroup name="banner" tag="div" class="banner-zone">
24
+ <InfoBanner
25
+ v-for="banner in banners"
26
+ :key="banner.id"
27
+ :severity="banner.severity"
28
+ :icon="banner.icon"
29
+ :closable="banner.closable"
30
+ @close="hideBanner(banner.id)"
31
+ >
32
+ <span v-html="banner.message"></span>
33
+ </InfoBanner>
34
+ </TransitionGroup>
35
+ </template>
36
+
37
+ <style scoped>
38
+ /*
39
+ * Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
40
+ * Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
41
+ */
42
+ .banner-zone {
43
+ position: relative;
44
+ }
45
+
46
+ /* Transition animations */
47
+ .banner-enter-active,
48
+ .banner-leave-active {
49
+ transition: all 0.3s ease;
50
+ }
51
+
52
+ .banner-enter-from {
53
+ opacity: 0;
54
+ transform: translateY(-10px);
55
+ }
56
+
57
+ .banner-leave-to {
58
+ opacity: 0;
59
+ transform: translateY(-10px);
60
+ }
61
+
62
+ .banner-move {
63
+ transition: transform 0.3s ease;
64
+ }
65
+ </style>
@@ -0,0 +1,72 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * CardsGrid - Dynamic card zone component
4
+ *
5
+ * Props:
6
+ * - cards: Array of card objects from useCardsZone composable
7
+ * - columns: Number of columns (2, 3, 4, or 'auto')
8
+ *
9
+ * Card types:
10
+ * 1. Simple stat card (default):
11
+ * { name: 'total', value: 42, label: 'Total', severity: 'success', icon: 'pi pi-check' }
12
+ *
13
+ * 2. Custom card with slot:
14
+ * { name: 'preview', custom: true }
15
+ * Then use: <template #card-preview>...</template>
16
+ *
17
+ * If no cards provided, renders nothing.
18
+ */
19
+
20
+ type Severity = 'success' | 'danger' | 'warning' | 'info'
21
+
22
+ interface Card {
23
+ name: string
24
+ value?: string | number
25
+ label?: string
26
+ severity?: Severity
27
+ icon?: string
28
+ custom?: boolean
29
+ class?: string
30
+ onClick?: () => void
31
+ }
32
+
33
+ interface Props {
34
+ cards?: Card[]
35
+ columns?: number | 'auto'
36
+ }
37
+
38
+ withDefaults(defineProps<Props>(), {
39
+ cards: () => [],
40
+ columns: 'auto'
41
+ })
42
+
43
+ function getSeverityClass(severity?: Severity): string {
44
+ if (!severity) return ''
45
+ return `stat-value--${severity}`
46
+ }
47
+ </script>
48
+
49
+ <template>
50
+ <div v-if="cards.length > 0" class="cards-grid" :class="`cards-grid--cols-${columns}`">
51
+ <template v-for="card in cards" :key="card.name">
52
+ <!-- Custom card: render slot -->
53
+ <div v-if="card.custom" class="card-custom" :class="card.class">
54
+ <slot :name="`card-${card.name}`" :card="card" ></slot>
55
+ </div>
56
+
57
+ <!-- Simple stat card -->
58
+ <div
59
+ v-else
60
+ class="stat-card"
61
+ :class="[{ 'stat-card--clickable': card.onClick }, card.class]"
62
+ @click="card.onClick?.()"
63
+ >
64
+ <div class="stat-value" :class="getSeverityClass(card.severity)">
65
+ <i v-if="card.icon" :class="card.icon" class="stat-icon"></i>
66
+ {{ card.value }}
67
+ </div>
68
+ <div class="stat-label">{{ card.label }}</div>
69
+ </div>
70
+ </template>
71
+ </div>
72
+ </template>