qdadm 0.48.0 → 0.49.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qdadm",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Vue 3 framework for admin dashboards with PrimeVue",
5
5
  "author": "quazardous",
6
6
  "license": "MIT",
@@ -17,9 +17,9 @@
17
17
  * - entity: Current entity data (for dynamic labels in breadcrumb)
18
18
  * - parentEntity: Parent entity data (for parent label in breadcrumb)
19
19
  */
20
- import { computed, ref, watch, onMounted, onUnmounted, inject } from 'vue'
20
+ import { computed, ref, watch, inject } from 'vue'
21
21
  import { useRoute, useRouter } from 'vue-router'
22
- import { getSiblingRoutes } from '../../module/moduleRegistry.js'
22
+ import { getSiblingRoutes, getChildRoutes } from '../../module/moduleRegistry.js'
23
23
  import { useOrchestrator } from '../../orchestrator/useOrchestrator.js'
24
24
 
25
25
  // Inject override refs from AppLayout
@@ -129,7 +129,7 @@ const breadcrumbItems = computed(() => {
129
129
  })
130
130
 
131
131
  // Sibling navlinks (routes with same parent)
132
- const navlinks = computed(() => {
132
+ const siblingNavlinks = computed(() => {
133
133
  if (!parentConfig.value) return []
134
134
 
135
135
  const { entity: parentEntityName, param } = parentConfig.value
@@ -148,52 +148,91 @@ const navlinks = computed(() => {
148
148
  })
149
149
  })
150
150
 
151
- // Also include parent "Details" link
151
+ // Child navlinks (when on parent detail page, show links to children)
152
+ const childNavlinks = computed(() => {
153
+ // Only when NOT on a child route (no parentConfig)
154
+ if (parentConfig.value) return []
155
+
156
+ const entityName = route.meta?.entity
157
+ if (!entityName) return []
158
+
159
+ const children = getChildRoutes(entityName)
160
+ if (children.length === 0) return []
161
+
162
+ const entityId = route.params.id
163
+
164
+ // Build navlinks to child routes
165
+ return children.map(childRoute => {
166
+ const childManager = childRoute.meta?.entity ? getManager(childRoute.meta.entity) : null
167
+ const label = childRoute.meta?.navLabel || childManager?.labelPlural || childRoute.name
168
+ const parentParam = childRoute.meta?.parent?.param || 'id'
169
+
170
+ return {
171
+ label,
172
+ to: { name: childRoute.name, params: { [parentParam]: entityId } },
173
+ active: false
174
+ }
175
+ })
176
+ })
177
+
178
+ // Combined navlinks with "Details" link
152
179
  const allNavlinks = computed(() => {
153
- if (!parentConfig.value) return []
180
+ // Case 1: On a child route - show siblings + Details link to parent
181
+ if (parentConfig.value) {
182
+ const { entity: parentEntityName, param, itemRoute } = parentConfig.value
183
+ const parentId = route.params[param]
184
+ const parentManager = getManager(parentEntityName)
154
185
 
155
- const { entity: parentEntityName, param, itemRoute } = parentConfig.value
156
- const parentId = route.params[param]
157
- const parentManager = getManager(parentEntityName)
186
+ if (!parentManager) return siblingNavlinks.value
158
187
 
159
- if (!parentManager) return navlinks.value
188
+ const defaultSuffix = parentManager.readOnly ? '-show' : '-edit'
189
+ const parentRouteName = itemRoute || `${parentManager.routePrefix}${defaultSuffix}`
190
+ const isOnParentRoute = route.name === parentRouteName
191
+
192
+ // Details link to parent item page
193
+ // CONVENTION: Entity item routes MUST use :id as param name (e.g., /books/:id)
194
+ // This is a qdadm convention - all entity detail/edit routes expect 'id' param
195
+ const detailsLink = {
196
+ label: 'Details',
197
+ to: { name: parentRouteName, params: { id: parentId } },
198
+ active: isOnParentRoute
199
+ }
160
200
 
161
- const defaultSuffix = parentManager.readOnly ? '-show' : '-edit'
162
- const parentRouteName = itemRoute || `${parentManager.routePrefix}${defaultSuffix}`
163
- const isOnParentRoute = route.name === parentRouteName
201
+ return [detailsLink, ...siblingNavlinks.value]
202
+ }
164
203
 
165
- // Details link to parent item page
166
- const detailsLink = {
167
- label: 'Details',
168
- to: { name: parentRouteName, params: { id: parentId } },
169
- active: isOnParentRoute
204
+ // Case 2: On parent detail page - show children + Details (active)
205
+ if (childNavlinks.value.length > 0) {
206
+ const detailsLink = {
207
+ label: 'Details',
208
+ to: { name: route.name, params: route.params },
209
+ active: true
210
+ }
211
+
212
+ return [detailsLink, ...childNavlinks.value]
170
213
  }
171
214
 
172
- return [detailsLink, ...navlinks.value]
215
+ return []
173
216
  })
174
217
 
175
218
  // Sync breadcrumb and navlinks to AppLayout via provide/inject
176
- watch(breadcrumbItems, (items) => {
219
+ // Watch computed values + route changes to ensure updates after navigation
220
+ watch([breadcrumbItems, () => route.fullPath], ([items]) => {
177
221
  if (breadcrumbOverride) {
178
222
  breadcrumbOverride.value = items
179
223
  }
180
224
  }, { immediate: true })
181
225
 
182
- watch(allNavlinks, (links) => {
226
+ watch([allNavlinks, () => route.fullPath], ([links]) => {
183
227
  if (navlinksOverride) {
184
228
  navlinksOverride.value = links
185
229
  }
186
230
  }, { immediate: true })
187
231
 
188
- // Clear overrides when component unmounts (so other pages get default breadcrumb)
189
- onUnmounted(() => {
190
- if (breadcrumbOverride) {
191
- breadcrumbOverride.value = null
192
- }
193
- if (navlinksOverride) {
194
- navlinksOverride.value = null
195
- }
196
- })
232
+ // Note: We intentionally do NOT clear overrides in onUnmounted.
233
+ // When navigating between routes, the new PageNav's watch will overwrite the values.
234
+ // Clearing in onUnmounted causes a race condition where the old PageNav clears
235
+ // AFTER the new PageNav has already set its values.
197
236
  </script>
198
237
 
199
238
  <template>
@@ -49,13 +49,21 @@ let alterationPromise = null
49
49
  const registry = {
50
50
  /**
51
51
  * Add routes for this module
52
- * @param {string} prefix - Path prefix for all routes (e.g., 'agents')
52
+ *
53
+ * CONVENTION: Entity item routes MUST use :id as param name
54
+ * - List route: 'books' → /books
55
+ * - Item route: 'books/:id' → /books/:id (MUST be :id, not :bookId or :uuid)
56
+ * - Child route: 'books/:id/reviews' → parent.param = 'id'
57
+ *
58
+ * This convention is required for PageNav, breadcrumbs, and navigation to work correctly.
59
+ *
60
+ * @param {string} prefix - Path prefix for all routes (e.g., 'books' or 'books/:id/reviews')
53
61
  * @param {Array} moduleRoutes - Route definitions with relative paths
54
62
  * @param {object} options - Route options
55
63
  * @param {string} [options.entity] - Entity name for permission checking
56
64
  * @param {object} [options.parent] - Parent entity config for child routes
57
65
  * @param {string} options.parent.entity - Parent entity name (e.g., 'books')
58
- * @param {string} options.parent.param - Route param for parent ID (e.g., 'bookId')
66
+ * @param {string} options.parent.param - Route param for parent ID (MUST be 'id')
59
67
  * @param {string} options.parent.foreignKey - Foreign key field (e.g., 'book_id')
60
68
  * @param {string} [options.parent.itemRoute] - Override parent item route (auto: parentEntity.routePrefix + '-edit')
61
69
  * @param {string} [options.label] - Label for navlinks (defaults to entity labelPlural)
@@ -341,6 +349,18 @@ export function getSiblingRoutes(parentEntity, parentParam) {
341
349
  })
342
350
  }
343
351
 
352
+ /**
353
+ * Get child routes (routes that have this entity as parent)
354
+ * @param {string} entityName - Entity name to find children for
355
+ * @returns {Array} Routes with this entity as parent
356
+ */
357
+ export function getChildRoutes(entityName) {
358
+ return routes.filter(route => {
359
+ const parent = route.meta?.parent
360
+ return parent?.entity === entityName
361
+ })
362
+ }
363
+
344
364
  /**
345
365
  * Check if a route belongs to a family
346
366
  */