qdadm 0.48.0 → 0.49.1
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
|
@@ -177,9 +177,12 @@ function setBreadcrumbEntity(data, level = 1) {
|
|
|
177
177
|
provide('qdadmSetBreadcrumbEntity', setBreadcrumbEntity)
|
|
178
178
|
provide('qdadmBreadcrumbEntities', breadcrumbEntities)
|
|
179
179
|
|
|
180
|
-
// Clear entity data on route change (before new page mounts)
|
|
180
|
+
// Clear entity data and overrides on route change (before new page mounts)
|
|
181
|
+
// This ensures list pages get default breadcrumb, detail pages can override via PageNav
|
|
181
182
|
watch(() => route.fullPath, () => {
|
|
182
183
|
breadcrumbEntities.value = new Map()
|
|
184
|
+
breadcrumbOverride.value = null
|
|
185
|
+
navlinksOverride.value = null
|
|
183
186
|
})
|
|
184
187
|
|
|
185
188
|
// Navigation context (breadcrumb + navlinks from route config)
|
|
@@ -17,15 +17,17 @@
|
|
|
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,
|
|
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
|
|
26
26
|
const breadcrumbOverride = inject('qdadmBreadcrumbOverride', null)
|
|
27
27
|
const navlinksOverride = inject('qdadmNavlinksOverride', null)
|
|
28
28
|
const homeRouteName = inject('qdadmHomeRoute', null)
|
|
29
|
+
// Entity data set by useEntityItemPage via setBreadcrumbEntity
|
|
30
|
+
const breadcrumbEntities = inject('qdadmBreadcrumbEntities', null)
|
|
29
31
|
|
|
30
32
|
const props = defineProps({
|
|
31
33
|
entity: { type: Object, default: null },
|
|
@@ -85,10 +87,22 @@ const breadcrumbItems = computed(() => {
|
|
|
85
87
|
if (entityName) {
|
|
86
88
|
const manager = getManager(entityName)
|
|
87
89
|
if (manager) {
|
|
90
|
+
// Entity list link
|
|
88
91
|
items.push({
|
|
89
92
|
label: manager.labelPlural || manager.name,
|
|
90
93
|
to: { name: manager.routePrefix }
|
|
91
94
|
})
|
|
95
|
+
|
|
96
|
+
// If on detail page (has :id param), add current entity item
|
|
97
|
+
const entityId = route.params.id
|
|
98
|
+
if (entityId) {
|
|
99
|
+
// Get entity data from props or from breadcrumbEntities (set by useEntityItemPage)
|
|
100
|
+
const entityData = props.entity || breadcrumbEntities?.value?.get(1)
|
|
101
|
+
const entityLabel = entityData
|
|
102
|
+
? manager.getEntityLabel(entityData)
|
|
103
|
+
: '...'
|
|
104
|
+
items.push({ label: entityLabel })
|
|
105
|
+
}
|
|
92
106
|
}
|
|
93
107
|
}
|
|
94
108
|
return items
|
|
@@ -129,7 +143,7 @@ const breadcrumbItems = computed(() => {
|
|
|
129
143
|
})
|
|
130
144
|
|
|
131
145
|
// Sibling navlinks (routes with same parent)
|
|
132
|
-
const
|
|
146
|
+
const siblingNavlinks = computed(() => {
|
|
133
147
|
if (!parentConfig.value) return []
|
|
134
148
|
|
|
135
149
|
const { entity: parentEntityName, param } = parentConfig.value
|
|
@@ -148,52 +162,91 @@ const navlinks = computed(() => {
|
|
|
148
162
|
})
|
|
149
163
|
})
|
|
150
164
|
|
|
151
|
-
//
|
|
165
|
+
// Child navlinks (when on parent detail page, show links to children)
|
|
166
|
+
const childNavlinks = computed(() => {
|
|
167
|
+
// Only when NOT on a child route (no parentConfig)
|
|
168
|
+
if (parentConfig.value) return []
|
|
169
|
+
|
|
170
|
+
const entityName = route.meta?.entity
|
|
171
|
+
if (!entityName) return []
|
|
172
|
+
|
|
173
|
+
const children = getChildRoutes(entityName)
|
|
174
|
+
if (children.length === 0) return []
|
|
175
|
+
|
|
176
|
+
const entityId = route.params.id
|
|
177
|
+
|
|
178
|
+
// Build navlinks to child routes
|
|
179
|
+
return children.map(childRoute => {
|
|
180
|
+
const childManager = childRoute.meta?.entity ? getManager(childRoute.meta.entity) : null
|
|
181
|
+
const label = childRoute.meta?.navLabel || childManager?.labelPlural || childRoute.name
|
|
182
|
+
const parentParam = childRoute.meta?.parent?.param || 'id'
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
label,
|
|
186
|
+
to: { name: childRoute.name, params: { [parentParam]: entityId } },
|
|
187
|
+
active: false
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Combined navlinks with "Details" link
|
|
152
193
|
const allNavlinks = computed(() => {
|
|
153
|
-
|
|
194
|
+
// Case 1: On a child route - show siblings + Details link to parent
|
|
195
|
+
if (parentConfig.value) {
|
|
196
|
+
const { entity: parentEntityName, param, itemRoute } = parentConfig.value
|
|
197
|
+
const parentId = route.params[param]
|
|
198
|
+
const parentManager = getManager(parentEntityName)
|
|
154
199
|
|
|
155
|
-
|
|
156
|
-
const parentId = route.params[param]
|
|
157
|
-
const parentManager = getManager(parentEntityName)
|
|
200
|
+
if (!parentManager) return siblingNavlinks.value
|
|
158
201
|
|
|
159
|
-
|
|
202
|
+
const defaultSuffix = parentManager.readOnly ? '-show' : '-edit'
|
|
203
|
+
const parentRouteName = itemRoute || `${parentManager.routePrefix}${defaultSuffix}`
|
|
204
|
+
const isOnParentRoute = route.name === parentRouteName
|
|
205
|
+
|
|
206
|
+
// Details link to parent item page
|
|
207
|
+
// CONVENTION: Entity item routes MUST use :id as param name (e.g., /books/:id)
|
|
208
|
+
// This is a qdadm convention - all entity detail/edit routes expect 'id' param
|
|
209
|
+
const detailsLink = {
|
|
210
|
+
label: 'Details',
|
|
211
|
+
to: { name: parentRouteName, params: { id: parentId } },
|
|
212
|
+
active: isOnParentRoute
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return [detailsLink, ...siblingNavlinks.value]
|
|
216
|
+
}
|
|
160
217
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
218
|
+
// Case 2: On parent detail page - show children + Details (active)
|
|
219
|
+
if (childNavlinks.value.length > 0) {
|
|
220
|
+
const detailsLink = {
|
|
221
|
+
label: 'Details',
|
|
222
|
+
to: { name: route.name, params: route.params },
|
|
223
|
+
active: true
|
|
224
|
+
}
|
|
164
225
|
|
|
165
|
-
|
|
166
|
-
const detailsLink = {
|
|
167
|
-
label: 'Details',
|
|
168
|
-
to: { name: parentRouteName, params: { id: parentId } },
|
|
169
|
-
active: isOnParentRoute
|
|
226
|
+
return [detailsLink, ...childNavlinks.value]
|
|
170
227
|
}
|
|
171
228
|
|
|
172
|
-
return [
|
|
229
|
+
return []
|
|
173
230
|
})
|
|
174
231
|
|
|
175
232
|
// Sync breadcrumb and navlinks to AppLayout via provide/inject
|
|
176
|
-
|
|
233
|
+
// Watch computed values + route changes + entity data to ensure updates
|
|
234
|
+
watch([breadcrumbItems, () => route.fullPath, breadcrumbEntities], ([items]) => {
|
|
177
235
|
if (breadcrumbOverride) {
|
|
178
236
|
breadcrumbOverride.value = items
|
|
179
237
|
}
|
|
180
|
-
}, { immediate: true })
|
|
238
|
+
}, { immediate: true, deep: true })
|
|
181
239
|
|
|
182
|
-
watch(allNavlinks, (links) => {
|
|
240
|
+
watch([allNavlinks, () => route.fullPath], ([links]) => {
|
|
183
241
|
if (navlinksOverride) {
|
|
184
242
|
navlinksOverride.value = links
|
|
185
243
|
}
|
|
186
244
|
}, { immediate: true })
|
|
187
245
|
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
if (navlinksOverride) {
|
|
194
|
-
navlinksOverride.value = null
|
|
195
|
-
}
|
|
196
|
-
})
|
|
246
|
+
// Note: We intentionally do NOT clear overrides in onUnmounted.
|
|
247
|
+
// When navigating between routes, the new PageNav's watch will overwrite the values.
|
|
248
|
+
// Clearing in onUnmounted causes a race condition where the old PageNav clears
|
|
249
|
+
// AFTER the new PageNav has already set its values.
|
|
197
250
|
</script>
|
|
198
251
|
|
|
199
252
|
<template>
|
|
@@ -49,13 +49,21 @@ let alterationPromise = null
|
|
|
49
49
|
const registry = {
|
|
50
50
|
/**
|
|
51
51
|
* Add routes for this module
|
|
52
|
-
*
|
|
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 (
|
|
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
|
*/
|