qdadm 0.16.0 → 0.18.0

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 (60) hide show
  1. package/README.md +153 -1
  2. package/package.json +15 -2
  3. package/src/components/forms/FormField.vue +64 -6
  4. package/src/components/forms/FormPage.vue +276 -0
  5. package/src/components/index.js +11 -0
  6. package/src/components/layout/BaseLayout.vue +183 -0
  7. package/src/components/layout/DashboardLayout.vue +100 -0
  8. package/src/components/layout/FormLayout.vue +261 -0
  9. package/src/components/layout/ListLayout.vue +334 -0
  10. package/src/components/layout/Zone.vue +165 -0
  11. package/src/components/layout/defaults/DefaultBreadcrumb.vue +140 -0
  12. package/src/components/layout/defaults/DefaultFooter.vue +56 -0
  13. package/src/components/layout/defaults/DefaultFormActions.vue +53 -0
  14. package/src/components/layout/defaults/DefaultHeader.vue +69 -0
  15. package/src/components/layout/defaults/DefaultMenu.vue +197 -0
  16. package/src/components/layout/defaults/DefaultPagination.vue +79 -0
  17. package/src/components/layout/defaults/DefaultTable.vue +130 -0
  18. package/src/components/layout/defaults/DefaultToaster.vue +16 -0
  19. package/src/components/layout/defaults/DefaultUserInfo.vue +96 -0
  20. package/src/components/layout/defaults/index.js +17 -0
  21. package/src/composables/index.js +6 -6
  22. package/src/composables/useForm.js +135 -0
  23. package/src/composables/useFormPageBuilder.js +1154 -0
  24. package/src/composables/useHooks.js +53 -0
  25. package/src/composables/useLayoutResolver.js +260 -0
  26. package/src/composables/useListPageBuilder.js +336 -52
  27. package/src/composables/useNavigation.js +38 -2
  28. package/src/composables/useSignals.js +49 -0
  29. package/src/composables/useZoneRegistry.js +162 -0
  30. package/src/core/bundles.js +406 -0
  31. package/src/core/decorator.js +322 -0
  32. package/src/core/extension.js +386 -0
  33. package/src/core/index.js +28 -0
  34. package/src/entity/EntityManager.js +359 -16
  35. package/src/entity/auth/AuthAdapter.js +184 -0
  36. package/src/entity/auth/PermissiveAdapter.js +64 -0
  37. package/src/entity/auth/RoleHierarchy.js +153 -0
  38. package/src/entity/auth/SecurityChecker.js +167 -0
  39. package/src/entity/auth/index.js +18 -0
  40. package/src/entity/index.js +3 -0
  41. package/src/entity/storage/MockApiStorage.js +349 -0
  42. package/src/entity/storage/SdkStorage.js +478 -0
  43. package/src/entity/storage/index.js +2 -0
  44. package/src/hooks/HookRegistry.js +411 -0
  45. package/src/hooks/index.js +12 -0
  46. package/src/index.js +13 -0
  47. package/src/kernel/Kernel.js +206 -5
  48. package/src/kernel/SignalBus.js +180 -0
  49. package/src/kernel/index.js +7 -0
  50. package/src/module/moduleRegistry.js +155 -28
  51. package/src/orchestrator/Orchestrator.js +73 -1
  52. package/src/zones/ZoneRegistry.js +828 -0
  53. package/src/zones/index.js +16 -0
  54. package/src/zones/zones.js +189 -0
  55. package/src/composables/useEntityTitle.js +0 -121
  56. package/src/composables/useManager.js +0 -20
  57. package/src/composables/usePageBuilder.js +0 -334
  58. package/src/composables/useStatus.js +0 -146
  59. package/src/composables/useSubEditor.js +0 -165
  60. package/src/composables/useTabSync.js +0 -110
@@ -0,0 +1,183 @@
1
+ <script setup>
2
+ /**
3
+ * BaseLayout - Root of the 3-level layout inheritance chain
4
+ *
5
+ * Defines all standard zones for the admin application:
6
+ * - header: Top bar with branding
7
+ * - menu: Navigation sidebar
8
+ * - breadcrumb: Breadcrumb trail
9
+ * - sidebar: Optional secondary sidebar (empty by default)
10
+ * - main: Primary content area (slot for child layouts)
11
+ * - footer: Footer with optional branding
12
+ * - toaster: Toast notifications overlay
13
+ * - user-info: User info in sidebar (separate from footer)
14
+ *
15
+ * Each zone renders blocks from ZoneRegistry, with defaults
16
+ * provided when no blocks are registered.
17
+ *
18
+ * Inheritance pattern (Twig-style):
19
+ * BaseLayout renders Zone components for each area
20
+ * -> Child layouts (List, Form, Dashboard) extend by providing content to main zone
21
+ * -> Entity pages extend child layouts with entity-specific customizations
22
+ *
23
+ * Usage:
24
+ * <BaseLayout>
25
+ * <template #main>
26
+ * <!-- Page content here -->
27
+ * </template>
28
+ * </BaseLayout>
29
+ *
30
+ * Or with RouterView for nested routes:
31
+ * <BaseLayout />
32
+ */
33
+ import { useSlots, provide, ref, inject } from 'vue'
34
+ import { RouterView } from 'vue-router'
35
+ import ConfirmDialog from 'primevue/confirmdialog'
36
+ import Zone from './Zone.vue'
37
+ import { LAYOUT_ZONES } from '../../zones/zones.js'
38
+ import { useGuardDialog } from '../../composables/useGuardStore'
39
+ import UnsavedChangesDialog from '../dialogs/UnsavedChangesDialog.vue'
40
+
41
+ // Default components for zones
42
+ import DefaultHeader from './defaults/DefaultHeader.vue'
43
+ import DefaultMenu from './defaults/DefaultMenu.vue'
44
+ import DefaultFooter from './defaults/DefaultFooter.vue'
45
+ import DefaultUserInfo from './defaults/DefaultUserInfo.vue'
46
+ import DefaultBreadcrumb from './defaults/DefaultBreadcrumb.vue'
47
+ import DefaultToaster from './defaults/DefaultToaster.vue'
48
+
49
+ const slots = useSlots()
50
+ const hasMainSlot = !!slots.main
51
+
52
+ // Guard dialog from shared store (registered by useBareForm/useForm when a form is active)
53
+ const guardDialog = useGuardDialog()
54
+
55
+ // Provide breadcrumb/navlinks override mechanism for child pages
56
+ const breadcrumbOverride = ref(null)
57
+ const navlinksOverride = ref(null)
58
+ provide('qdadmBreadcrumbOverride', breadcrumbOverride)
59
+ provide('qdadmNavlinksOverride', navlinksOverride)
60
+ </script>
61
+
62
+ <template>
63
+ <div class="base-layout">
64
+ <!-- Sidebar (contains header, menu, user-info, footer) -->
65
+ <aside class="sidebar">
66
+ <!-- Header zone: branding, logo -->
67
+ <Zone
68
+ :name="LAYOUT_ZONES.HEADER"
69
+ :default-component="DefaultHeader"
70
+ />
71
+
72
+ <!-- Menu zone: navigation -->
73
+ <Zone
74
+ :name="LAYOUT_ZONES.MENU"
75
+ :default-component="DefaultMenu"
76
+ />
77
+
78
+ <!-- User info (special zone between menu and footer) -->
79
+ <DefaultUserInfo />
80
+
81
+ <!-- Footer zone: powered by, etc. -->
82
+ <Zone
83
+ :name="LAYOUT_ZONES.FOOTER"
84
+ :default-component="DefaultFooter"
85
+ />
86
+ </aside>
87
+
88
+ <!-- Main content area -->
89
+ <div class="main-area">
90
+ <!-- Breadcrumb zone -->
91
+ <Zone
92
+ :name="LAYOUT_ZONES.BREADCRUMB"
93
+ :default-component="DefaultBreadcrumb"
94
+ />
95
+
96
+ <!-- Sidebar zone (optional secondary sidebar) -->
97
+ <div class="content-with-sidebar">
98
+ <Zone :name="LAYOUT_ZONES.SIDEBAR" />
99
+
100
+ <!-- Main zone: primary content -->
101
+ <main class="main-content">
102
+ <Zone :name="LAYOUT_ZONES.MAIN">
103
+ <!-- Allow slot override or RouterView for nested routes -->
104
+ <template v-if="hasMainSlot">
105
+ <slot name="main" />
106
+ </template>
107
+ <RouterView v-else />
108
+ </Zone>
109
+ </main>
110
+ </div>
111
+ </div>
112
+
113
+ <!-- Toaster zone: toast notifications -->
114
+ <Zone
115
+ :name="LAYOUT_ZONES.TOASTER"
116
+ :default-component="DefaultToaster"
117
+ />
118
+
119
+ <!-- Confirm dialog (global) -->
120
+ <ConfirmDialog />
121
+
122
+ <!-- Unsaved Changes Dialog (auto-rendered when a form registers guardDialog) -->
123
+ <UnsavedChangesDialog
124
+ v-if="guardDialog"
125
+ :visible="guardDialog.visible.value"
126
+ :saving="guardDialog.saving.value"
127
+ :message="guardDialog.message"
128
+ :hasOnSave="guardDialog.hasOnSave"
129
+ @saveAndLeave="guardDialog.onSaveAndLeave"
130
+ @leave="guardDialog.onLeave"
131
+ @stay="guardDialog.onStay"
132
+ />
133
+ </div>
134
+ </template>
135
+
136
+ <style scoped>
137
+ .base-layout {
138
+ display: flex;
139
+ min-height: 100vh;
140
+ }
141
+
142
+ .sidebar {
143
+ width: var(--fad-sidebar-width, 15rem);
144
+ background: var(--p-surface-800, #1e293b);
145
+ color: var(--p-surface-0, white);
146
+ display: flex;
147
+ flex-direction: column;
148
+ position: fixed;
149
+ top: 0;
150
+ left: 0;
151
+ bottom: 0;
152
+ z-index: 100;
153
+ }
154
+
155
+ .main-area {
156
+ flex: 1;
157
+ margin-left: var(--fad-sidebar-width, 15rem);
158
+ background: var(--p-surface-50, #f8fafc);
159
+ min-height: 100vh;
160
+ display: flex;
161
+ flex-direction: column;
162
+ }
163
+
164
+ .content-with-sidebar {
165
+ display: flex;
166
+ flex: 1;
167
+ }
168
+
169
+ .main-content {
170
+ flex: 1;
171
+ padding: 1.5rem;
172
+ overflow-y: auto;
173
+ }
174
+
175
+ /* Dark mode support */
176
+ .dark-mode .sidebar {
177
+ background: var(--p-surface-900);
178
+ }
179
+
180
+ .dark-mode .main-area {
181
+ background: var(--p-surface-900);
182
+ }
183
+ </style>
@@ -0,0 +1,100 @@
1
+ <script setup>
2
+ /**
3
+ * DashboardLayout - Second level of layout inheritance for dashboard pages
4
+ *
5
+ * Extends BaseLayout by providing content to its `main` zone and defining
6
+ * dashboard-specific zones:
7
+ * - stats: Top area for KPI cards (CardsGrid component)
8
+ * - widgets: Main dashboard widgets area
9
+ * - recent-activity: Recent changes and activity log
10
+ *
11
+ * Each zone renders blocks from ZoneRegistry. Empty zones render nothing
12
+ * (no default widgets). Applications provide dashboard content by
13
+ * registering blocks to these zones.
14
+ *
15
+ * Inheritance pattern:
16
+ * BaseLayout renders Zone components for each area
17
+ * -> DashboardLayout fills main zone with dashboard structure
18
+ * -> Dashboard pages extend with entity-specific customizations
19
+ *
20
+ * Usage:
21
+ * <DashboardLayout>
22
+ * <!-- Optional: Override specific zones via slots -->
23
+ * </DashboardLayout>
24
+ *
25
+ * Or register blocks via ZoneRegistry:
26
+ * registry.registerBlock(DASHBOARD_ZONES.STATS, {
27
+ * component: StatsWidget,
28
+ * id: 'kpi-cards',
29
+ * weight: 10
30
+ * })
31
+ */
32
+ import { useSlots } from 'vue'
33
+ import BaseLayout from './BaseLayout.vue'
34
+ import Zone from './Zone.vue'
35
+ import { DASHBOARD_ZONES } from '../../zones/zones.js'
36
+
37
+ const slots = useSlots()
38
+ </script>
39
+
40
+ <template>
41
+ <BaseLayout>
42
+ <template #main>
43
+ <div class="dashboard-layout">
44
+ <!-- Stats zone: KPI cards, metrics -->
45
+ <section v-if="slots.stats" class="dashboard-stats">
46
+ <slot name="stats" />
47
+ </section>
48
+ <section v-else class="dashboard-stats">
49
+ <Zone :name="DASHBOARD_ZONES.STATS" />
50
+ </section>
51
+
52
+ <!-- Widgets zone: main dashboard widgets -->
53
+ <section v-if="slots.widgets" class="dashboard-widgets">
54
+ <slot name="widgets" />
55
+ </section>
56
+ <section v-else class="dashboard-widgets">
57
+ <Zone :name="DASHBOARD_ZONES.WIDGETS" />
58
+ </section>
59
+
60
+ <!-- Recent activity zone: activity log, recent changes -->
61
+ <section v-if="slots['recent-activity']" class="dashboard-recent-activity">
62
+ <slot name="recent-activity" />
63
+ </section>
64
+ <section v-else class="dashboard-recent-activity">
65
+ <Zone :name="DASHBOARD_ZONES.RECENT_ACTIVITY" />
66
+ </section>
67
+ </div>
68
+ </template>
69
+ </BaseLayout>
70
+ </template>
71
+
72
+ <style scoped>
73
+ .dashboard-layout {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 1.5rem;
77
+ }
78
+
79
+ .dashboard-stats {
80
+ /* Stats area typically uses CardsGrid */
81
+ }
82
+
83
+ .dashboard-widgets {
84
+ /* Main widgets area */
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 1rem;
88
+ }
89
+
90
+ .dashboard-recent-activity {
91
+ /* Recent activity area */
92
+ }
93
+
94
+ /* Empty zones should collapse - Zone renders nothing when no blocks */
95
+ .dashboard-stats:empty,
96
+ .dashboard-widgets:empty,
97
+ .dashboard-recent-activity:empty {
98
+ display: none;
99
+ }
100
+ </style>
@@ -0,0 +1,261 @@
1
+ <script setup>
2
+ /**
3
+ * FormLayout - Second level of the 3-level layout inheritance chain
4
+ *
5
+ * Extends BaseLayout by providing content to the `main` zone, and defines
6
+ * form-specific zones within that main area:
7
+ * - form-header: Title, back button, status indicators
8
+ * - form-fields: Main form fields area
9
+ * - form-tabs: Optional tabbed sections
10
+ * - actions: Save, cancel, delete buttons
11
+ *
12
+ * Inheritance pattern (Twig-style):
13
+ * BaseLayout renders all standard zones (header, menu, footer, main...)
14
+ * -> FormLayout fills the main zone with form structure
15
+ * -> Entity pages (BooksEditPage.vue) fill form zones with entity fields
16
+ *
17
+ * Usage:
18
+ * <FormLayout>
19
+ * <template #form-fields>
20
+ * <FormField v-model="data.title" name="title" />
21
+ * </template>
22
+ * <template #actions>
23
+ * <Button @click="save">Save</Button>
24
+ * </template>
25
+ * </FormLayout>
26
+ *
27
+ * Or using Zone API for extension points:
28
+ * <FormLayout /> <!-- Renders zones from registry -->
29
+ */
30
+ import { useSlots, provide, computed } from 'vue'
31
+ import BaseLayout from './BaseLayout.vue'
32
+ import Zone from './Zone.vue'
33
+ import { FORM_ZONES } from '../../zones/zones.js'
34
+
35
+ // Default components for form zones
36
+ import DefaultFormActions from './defaults/DefaultFormActions.vue'
37
+
38
+ const props = defineProps({
39
+ /**
40
+ * Form loading state (displays spinner instead of fields)
41
+ */
42
+ loading: {
43
+ type: Boolean,
44
+ default: false
45
+ },
46
+ /**
47
+ * Form saving state (passed to actions)
48
+ */
49
+ saving: {
50
+ type: Boolean,
51
+ default: false
52
+ },
53
+ /**
54
+ * Form has unsaved changes (passed to actions)
55
+ */
56
+ dirty: {
57
+ type: Boolean,
58
+ default: false
59
+ },
60
+ /**
61
+ * Edit mode (changes action button labels)
62
+ */
63
+ isEdit: {
64
+ type: Boolean,
65
+ default: false
66
+ },
67
+ /**
68
+ * Whether to wrap form in a card component
69
+ */
70
+ cardWrapper: {
71
+ type: Boolean,
72
+ default: true
73
+ },
74
+ /**
75
+ * Whether to show the actions zone
76
+ */
77
+ showActions: {
78
+ type: Boolean,
79
+ default: true
80
+ }
81
+ })
82
+
83
+ const emit = defineEmits(['save', 'saveAndClose', 'cancel', 'delete'])
84
+
85
+ const slots = useSlots()
86
+
87
+ // Check which slots are provided by the parent
88
+ const hasFormHeaderSlot = computed(() => !!slots['form-header'])
89
+ const hasFormFieldsSlot = computed(() => !!slots['form-fields'])
90
+ const hasFormTabsSlot = computed(() => !!slots['form-tabs'])
91
+ const hasActionsSlot = computed(() => !!slots.actions)
92
+
93
+ // Provide form state to child zones (DefaultFormActions needs these)
94
+ provide('qdadmFormState', computed(() => ({
95
+ loading: props.loading,
96
+ saving: props.saving,
97
+ dirty: props.dirty,
98
+ isEdit: props.isEdit
99
+ })))
100
+
101
+ // Provide form events for child zones to emit
102
+ provide('qdadmFormEmit', {
103
+ save: () => emit('save'),
104
+ saveAndClose: () => emit('saveAndClose'),
105
+ cancel: () => emit('cancel'),
106
+ delete: () => emit('delete')
107
+ })
108
+ </script>
109
+
110
+ <template>
111
+ <BaseLayout>
112
+ <template #main>
113
+ <div class="form-layout" :class="{ loading }">
114
+ <!-- Form Header Zone: title, back button, status indicators -->
115
+ <div class="form-header-zone">
116
+ <Zone :name="FORM_ZONES.FORM_HEADER">
117
+ <template v-if="hasFormHeaderSlot">
118
+ <slot name="form-header" />
119
+ </template>
120
+ </Zone>
121
+ </div>
122
+
123
+ <!-- Loading State -->
124
+ <div v-if="loading" class="form-loading-state">
125
+ <i class="pi pi-spin pi-spinner loading-spinner"></i>
126
+ </div>
127
+
128
+ <!-- Form Content -->
129
+ <template v-else>
130
+ <!-- Card wrapper or direct content -->
131
+ <div v-if="cardWrapper" class="form-card">
132
+ <!-- Form Fields Zone: main form fields area -->
133
+ <div class="form-fields-zone">
134
+ <Zone :name="FORM_ZONES.FORM_FIELDS">
135
+ <template v-if="hasFormFieldsSlot">
136
+ <slot name="form-fields" />
137
+ </template>
138
+ </Zone>
139
+ </div>
140
+
141
+ <!-- Form Tabs Zone: optional tabbed sections -->
142
+ <div v-if="hasFormTabsSlot" class="form-tabs-zone">
143
+ <Zone :name="FORM_ZONES.FORM_TABS">
144
+ <slot name="form-tabs" />
145
+ </Zone>
146
+ </div>
147
+
148
+ <!-- Actions Zone: save, cancel, delete buttons -->
149
+ <div v-if="showActions" class="form-actions-zone">
150
+ <!-- Slot override takes priority over Zone -->
151
+ <template v-if="hasActionsSlot">
152
+ <slot name="actions" />
153
+ </template>
154
+ <!-- Otherwise use Zone with default component -->
155
+ <Zone
156
+ v-else
157
+ :name="FORM_ZONES.ACTIONS"
158
+ :default-component="DefaultFormActions"
159
+ />
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Without card wrapper -->
164
+ <template v-else>
165
+ <!-- Form Fields Zone -->
166
+ <div class="form-fields-zone">
167
+ <Zone :name="FORM_ZONES.FORM_FIELDS">
168
+ <template v-if="hasFormFieldsSlot">
169
+ <slot name="form-fields" />
170
+ </template>
171
+ </Zone>
172
+ </div>
173
+
174
+ <!-- Form Tabs Zone -->
175
+ <div v-if="hasFormTabsSlot" class="form-tabs-zone">
176
+ <Zone :name="FORM_ZONES.FORM_TABS">
177
+ <slot name="form-tabs" />
178
+ </Zone>
179
+ </div>
180
+
181
+ <!-- Actions Zone -->
182
+ <div v-if="showActions" class="form-actions-zone">
183
+ <!-- Slot override takes priority over Zone -->
184
+ <template v-if="hasActionsSlot">
185
+ <slot name="actions" />
186
+ </template>
187
+ <!-- Otherwise use Zone with default component -->
188
+ <Zone
189
+ v-else
190
+ :name="FORM_ZONES.ACTIONS"
191
+ :default-component="DefaultFormActions"
192
+ />
193
+ </div>
194
+ </template>
195
+ </template>
196
+ </div>
197
+ </template>
198
+ </BaseLayout>
199
+ </template>
200
+
201
+ <style scoped>
202
+ .form-layout {
203
+ display: flex;
204
+ flex-direction: column;
205
+ gap: 1rem;
206
+ }
207
+
208
+ .form-layout.loading {
209
+ min-height: 300px;
210
+ }
211
+
212
+ .form-loading-state {
213
+ display: flex;
214
+ justify-content: center;
215
+ align-items: center;
216
+ padding: 3rem;
217
+ flex: 1;
218
+ }
219
+
220
+ .loading-spinner {
221
+ font-size: 2rem;
222
+ color: var(--p-primary-500);
223
+ }
224
+
225
+ .form-card {
226
+ background: var(--p-surface-0);
227
+ border-radius: var(--p-border-radius);
228
+ padding: 1.5rem;
229
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
230
+ }
231
+
232
+ .form-header-zone {
233
+ /* Form header inherits page header styles */
234
+ }
235
+
236
+ .form-fields-zone {
237
+ display: flex;
238
+ flex-direction: column;
239
+ gap: 1rem;
240
+ }
241
+
242
+ .form-tabs-zone {
243
+ margin-top: 1rem;
244
+ }
245
+
246
+ .form-actions-zone {
247
+ margin-top: 1.5rem;
248
+ padding-top: 1.5rem;
249
+ border-top: 1px solid var(--p-surface-200);
250
+ }
251
+
252
+ /* Dark mode support */
253
+ .dark-mode .form-card {
254
+ background: var(--p-surface-800);
255
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
256
+ }
257
+
258
+ .dark-mode .form-actions-zone {
259
+ border-top-color: var(--p-surface-700);
260
+ }
261
+ </style>