qdadm 0.49.0 → 0.50.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
|
@@ -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)
|
|
@@ -26,10 +26,14 @@ import { useOrchestrator } from '../../orchestrator/useOrchestrator.js'
|
|
|
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 },
|
|
32
|
-
parentEntity: { type: Object, default: null }
|
|
34
|
+
parentEntity: { type: Object, default: null },
|
|
35
|
+
// Show "Details" link in navlinks (default: false since breadcrumb shows current page)
|
|
36
|
+
showDetailsLink: { type: Boolean, default: false }
|
|
33
37
|
})
|
|
34
38
|
|
|
35
39
|
const route = useRoute()
|
|
@@ -85,10 +89,22 @@ const breadcrumbItems = computed(() => {
|
|
|
85
89
|
if (entityName) {
|
|
86
90
|
const manager = getManager(entityName)
|
|
87
91
|
if (manager) {
|
|
92
|
+
// Entity list link
|
|
88
93
|
items.push({
|
|
89
94
|
label: manager.labelPlural || manager.name,
|
|
90
95
|
to: { name: manager.routePrefix }
|
|
91
96
|
})
|
|
97
|
+
|
|
98
|
+
// If on detail page (has :id param), add current entity item
|
|
99
|
+
const entityId = route.params.id
|
|
100
|
+
if (entityId) {
|
|
101
|
+
// Get entity data from props or from breadcrumbEntities (set by useEntityItemPage)
|
|
102
|
+
const entityData = props.entity || breadcrumbEntities?.value?.get(1)
|
|
103
|
+
const entityLabel = entityData
|
|
104
|
+
? manager.getEntityLabel(entityData)
|
|
105
|
+
: '...'
|
|
106
|
+
items.push({ label: entityLabel })
|
|
107
|
+
}
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
110
|
return items
|
|
@@ -107,8 +123,10 @@ const breadcrumbItems = computed(() => {
|
|
|
107
123
|
})
|
|
108
124
|
|
|
109
125
|
// Parent item (with label from data)
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
// Prefer breadcrumbEntities (set by useEntityItemPage) over local parentData
|
|
127
|
+
const parentEntityData = breadcrumbEntities?.value?.get(1) || parentData.value
|
|
128
|
+
const parentLabel = parentEntityData
|
|
129
|
+
? parentManager.getEntityLabel(parentEntityData)
|
|
112
130
|
: '...'
|
|
113
131
|
const defaultSuffix = parentManager.readOnly ? '-show' : '-edit'
|
|
114
132
|
const parentRouteName = itemRoute || `${parentManager.routePrefix}${defaultSuffix}`
|
|
@@ -135,8 +153,16 @@ const siblingNavlinks = computed(() => {
|
|
|
135
153
|
const { entity: parentEntityName, param } = parentConfig.value
|
|
136
154
|
const siblings = getSiblingRoutes(parentEntityName, param)
|
|
137
155
|
|
|
156
|
+
// Filter out action routes (create, edit) - they're not navigation tabs
|
|
157
|
+
const navRoutes = siblings.filter(r => {
|
|
158
|
+
const name = r.name || ''
|
|
159
|
+
const path = r.path || ''
|
|
160
|
+
// Exclude routes ending with -create, -edit, -new or paths with /create, /edit, /new
|
|
161
|
+
return !name.match(/-(create|edit|new)$/) && !path.match(/\/(create|edit|new)$/)
|
|
162
|
+
})
|
|
163
|
+
|
|
138
164
|
// Build navlinks with current route params
|
|
139
|
-
return
|
|
165
|
+
return navRoutes.map(siblingRoute => {
|
|
140
166
|
const manager = siblingRoute.meta?.entity ? getManager(siblingRoute.meta.entity) : null
|
|
141
167
|
const label = siblingRoute.meta?.navLabel || manager?.labelPlural || siblingRoute.name
|
|
142
168
|
|
|
@@ -159,10 +185,19 @@ const childNavlinks = computed(() => {
|
|
|
159
185
|
const children = getChildRoutes(entityName)
|
|
160
186
|
if (children.length === 0) return []
|
|
161
187
|
|
|
188
|
+
// Filter out action routes (create, edit) - they're not navigation tabs
|
|
189
|
+
const navRoutes = children.filter(r => {
|
|
190
|
+
const name = r.name || ''
|
|
191
|
+
const path = r.path || ''
|
|
192
|
+
return !name.match(/-(create|edit|new)$/) && !path.match(/\/(create|edit|new)$/)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
if (navRoutes.length === 0) return []
|
|
196
|
+
|
|
162
197
|
const entityId = route.params.id
|
|
163
198
|
|
|
164
199
|
// Build navlinks to child routes
|
|
165
|
-
return
|
|
200
|
+
return navRoutes.map(childRoute => {
|
|
166
201
|
const childManager = childRoute.meta?.entity ? getManager(childRoute.meta.entity) : null
|
|
167
202
|
const label = childRoute.meta?.navLabel || childManager?.labelPlural || childRoute.name
|
|
168
203
|
const parentParam = childRoute.meta?.parent?.param || 'id'
|
|
@@ -177,7 +212,7 @@ const childNavlinks = computed(() => {
|
|
|
177
212
|
|
|
178
213
|
// Combined navlinks with "Details" link
|
|
179
214
|
const allNavlinks = computed(() => {
|
|
180
|
-
// Case 1: On a child route - show siblings + Details link to parent
|
|
215
|
+
// Case 1: On a child route - show siblings + optional Details link to parent
|
|
181
216
|
if (parentConfig.value) {
|
|
182
217
|
const { entity: parentEntityName, param, itemRoute } = parentConfig.value
|
|
183
218
|
const parentId = route.params[param]
|
|
@@ -185,43 +220,50 @@ const allNavlinks = computed(() => {
|
|
|
185
220
|
|
|
186
221
|
if (!parentManager) return siblingNavlinks.value
|
|
187
222
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
223
|
+
// Details link is optional since breadcrumb already shows parent
|
|
224
|
+
if (props.showDetailsLink) {
|
|
225
|
+
const defaultSuffix = parentManager.readOnly ? '-show' : '-edit'
|
|
226
|
+
const parentRouteName = itemRoute || `${parentManager.routePrefix}${defaultSuffix}`
|
|
227
|
+
const isOnParentRoute = route.name === parentRouteName
|
|
228
|
+
|
|
229
|
+
// Details link to parent item page
|
|
230
|
+
// CONVENTION: Entity item routes MUST use :id as param name (e.g., /books/:id)
|
|
231
|
+
const detailsLink = {
|
|
232
|
+
label: 'Details',
|
|
233
|
+
to: { name: parentRouteName, params: { id: parentId } },
|
|
234
|
+
active: isOnParentRoute
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return [detailsLink, ...siblingNavlinks.value]
|
|
199
238
|
}
|
|
200
239
|
|
|
201
|
-
return
|
|
240
|
+
return siblingNavlinks.value
|
|
202
241
|
}
|
|
203
242
|
|
|
204
|
-
// Case 2: On parent detail page - show children + Details (active)
|
|
243
|
+
// Case 2: On parent detail page - show children + optional Details (active)
|
|
205
244
|
if (childNavlinks.value.length > 0) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
// Details link is optional since breadcrumb already shows current page
|
|
246
|
+
if (props.showDetailsLink) {
|
|
247
|
+
const detailsLink = {
|
|
248
|
+
label: 'Details',
|
|
249
|
+
to: { name: route.name, params: route.params },
|
|
250
|
+
active: true
|
|
251
|
+
}
|
|
252
|
+
return [detailsLink, ...childNavlinks.value]
|
|
210
253
|
}
|
|
211
|
-
|
|
212
|
-
return [detailsLink, ...childNavlinks.value]
|
|
254
|
+
return childNavlinks.value
|
|
213
255
|
}
|
|
214
256
|
|
|
215
257
|
return []
|
|
216
258
|
})
|
|
217
259
|
|
|
218
260
|
// Sync breadcrumb and navlinks to AppLayout via provide/inject
|
|
219
|
-
// Watch computed values + route changes to ensure updates
|
|
220
|
-
watch([breadcrumbItems, () => route.fullPath], ([items]) => {
|
|
261
|
+
// Watch computed values + route changes + entity data to ensure updates
|
|
262
|
+
watch([breadcrumbItems, () => route.fullPath, breadcrumbEntities], ([items]) => {
|
|
221
263
|
if (breadcrumbOverride) {
|
|
222
264
|
breadcrumbOverride.value = items
|
|
223
265
|
}
|
|
224
|
-
}, { immediate: true })
|
|
266
|
+
}, { immediate: true, deep: true })
|
|
225
267
|
|
|
226
268
|
watch([allNavlinks, () => route.fullPath], ([links]) => {
|
|
227
269
|
if (navlinksOverride) {
|
|
@@ -126,12 +126,18 @@ export function useEntityItemFormPage(config = {}) {
|
|
|
126
126
|
getId
|
|
127
127
|
})
|
|
128
128
|
|
|
129
|
-
const { manager, orchestrator, entityId, setBreadcrumbEntity } = itemPage
|
|
129
|
+
const { manager, orchestrator, entityId, setBreadcrumbEntity, getInitialDataWithParent, parentConfig, parentId, parentData, parentChain, getChainDepth } = itemPage
|
|
130
130
|
|
|
131
131
|
// Read config from manager with option overrides
|
|
132
132
|
const entityName = config.entityName ?? manager.label
|
|
133
133
|
const routePrefix = config.routePrefix ?? manager.routePrefix
|
|
134
|
-
|
|
134
|
+
|
|
135
|
+
// Initial data: merge user-provided with auto-populated parent foreignKey
|
|
136
|
+
// getInitialDataWithParent() adds the foreignKey from route.meta.parent
|
|
137
|
+
const baseInitialData = getInitialDataWithParent()
|
|
138
|
+
const initialData = config.initialData
|
|
139
|
+
? { ...baseInitialData, ...config.initialData }
|
|
140
|
+
: baseInitialData
|
|
135
141
|
|
|
136
142
|
/**
|
|
137
143
|
* Detect form mode: 'create' or 'edit'
|
|
@@ -1094,6 +1100,13 @@ export function useEntityItemFormPage(config = {}) {
|
|
|
1094
1100
|
isCreate,
|
|
1095
1101
|
entityId,
|
|
1096
1102
|
|
|
1103
|
+
// Parent chain (from route.meta.parent, supports N-level nesting)
|
|
1104
|
+
parentConfig,
|
|
1105
|
+
parentId,
|
|
1106
|
+
parentData, // Immediate parent (level 1)
|
|
1107
|
+
parentChain, // All parents: Map(level -> data)
|
|
1108
|
+
getChainDepth,
|
|
1109
|
+
|
|
1097
1110
|
// State
|
|
1098
1111
|
data,
|
|
1099
1112
|
loading,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - Loading/error state management
|
|
7
7
|
* - Breadcrumb integration (auto-calls setBreadcrumbEntity)
|
|
8
8
|
* - Manager access for child composables
|
|
9
|
+
* - **Parent chain auto-detection** from route.meta.parent
|
|
9
10
|
*
|
|
10
11
|
* Used as base for:
|
|
11
12
|
* - Show pages (read-only detail pages)
|
|
@@ -21,6 +22,21 @@
|
|
|
21
22
|
* // error.value contains error message if failed
|
|
22
23
|
* ```
|
|
23
24
|
*
|
|
25
|
+
* ## Parent Chain Auto-Detection
|
|
26
|
+
*
|
|
27
|
+
* When route.meta.parent is configured, the composable auto-loads the parent entity:
|
|
28
|
+
*
|
|
29
|
+
* ```js
|
|
30
|
+
* // Route: /authors/:id/books/create
|
|
31
|
+
* // route.meta.parent = { entity: 'authors', param: 'id', foreignKey: 'author_id' }
|
|
32
|
+
*
|
|
33
|
+
* const { parentData, parentId, getInitialDataWithParent } = useEntityItemPage({ entity: 'books' })
|
|
34
|
+
*
|
|
35
|
+
* // parentData.value = loaded author entity
|
|
36
|
+
* // parentId.value = route.params.id
|
|
37
|
+
* // getInitialDataWithParent() = { ...defaults, author_id: parentId }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
24
40
|
* ## Usage in parent composables
|
|
25
41
|
*
|
|
26
42
|
* ```js
|
|
@@ -45,6 +61,8 @@ export function useEntityItemPage(config = {}) {
|
|
|
45
61
|
// Loading options
|
|
46
62
|
loadOnMount = true,
|
|
47
63
|
breadcrumb = true,
|
|
64
|
+
// Parent chain options
|
|
65
|
+
autoLoadParent = true, // Auto-load parent entity from route.meta.parent
|
|
48
66
|
// ID extraction
|
|
49
67
|
getId = null,
|
|
50
68
|
idParam = 'id',
|
|
@@ -82,6 +100,135 @@ export function useEntityItemPage(config = {}) {
|
|
|
82
100
|
const loading = ref(false)
|
|
83
101
|
const error = ref(null)
|
|
84
102
|
|
|
103
|
+
// ============ PARENT CHAIN ============
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Parent configuration from route.meta.parent
|
|
107
|
+
* Structure: { entity, param, foreignKey, parent?: {...} }
|
|
108
|
+
* Supports nested parents for N-level chains
|
|
109
|
+
*/
|
|
110
|
+
const parentConfig = computed(() => route.meta?.parent || null)
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Parent entity ID from route params (based on parentConfig.param)
|
|
114
|
+
*/
|
|
115
|
+
const parentId = computed(() => {
|
|
116
|
+
if (!parentConfig.value) return null
|
|
117
|
+
return route.params[parentConfig.value.param] || null
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Loaded parent entities data (Map: level -> data)
|
|
122
|
+
* Level 1 = immediate parent, Level 2 = grandparent, etc.
|
|
123
|
+
*/
|
|
124
|
+
const parentChain = ref(new Map())
|
|
125
|
+
const parentLoading = ref(false)
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Immediate parent data (shortcut for level 1)
|
|
129
|
+
*/
|
|
130
|
+
const parentData = computed(() => parentChain.value.get(1) || null)
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Calculate the depth of the current entity in the parent chain
|
|
134
|
+
* Returns number of parent levels + 1 for current entity
|
|
135
|
+
*/
|
|
136
|
+
function getChainDepth(config = parentConfig.value) {
|
|
137
|
+
if (!config) return 1
|
|
138
|
+
let depth = 1
|
|
139
|
+
let current = config
|
|
140
|
+
while (current) {
|
|
141
|
+
depth++
|
|
142
|
+
current = current.parent
|
|
143
|
+
}
|
|
144
|
+
return depth
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get parent entity manager (if parent is configured)
|
|
149
|
+
*/
|
|
150
|
+
function getParentManager() {
|
|
151
|
+
if (!parentConfig.value) return null
|
|
152
|
+
return orchestrator.get(parentConfig.value.entity)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Load entire parent chain recursively
|
|
157
|
+
* Sets breadcrumb entities at correct levels (1 = top ancestor, N = immediate parent)
|
|
158
|
+
*/
|
|
159
|
+
async function loadParentChain() {
|
|
160
|
+
if (!parentConfig.value || !parentId.value) return
|
|
161
|
+
|
|
162
|
+
parentLoading.value = true
|
|
163
|
+
const newChain = new Map()
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// Calculate total depth to set correct breadcrumb levels
|
|
167
|
+
// If chain is: grandparent -> parent -> current
|
|
168
|
+
// grandparent = level 1, parent = level 2, current = level 3
|
|
169
|
+
const totalDepth = getChainDepth()
|
|
170
|
+
|
|
171
|
+
// Load chain from immediate parent up to root
|
|
172
|
+
let currentConfig = parentConfig.value
|
|
173
|
+
let level = 1 // Level in our chain (1 = immediate parent)
|
|
174
|
+
|
|
175
|
+
while (currentConfig) {
|
|
176
|
+
const parentEntityId = route.params[currentConfig.param]
|
|
177
|
+
if (!parentEntityId) break
|
|
178
|
+
|
|
179
|
+
const parentManager = orchestrator.get(currentConfig.entity)
|
|
180
|
+
if (!parentManager) {
|
|
181
|
+
console.warn(`[useEntityItemPage] Parent manager not found: ${currentConfig.entity}`)
|
|
182
|
+
break
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const data = await parentManager.get(parentEntityId)
|
|
186
|
+
if (data) {
|
|
187
|
+
newChain.set(level, data)
|
|
188
|
+
|
|
189
|
+
// Set breadcrumb at correct level
|
|
190
|
+
// breadcrumbLevel = totalDepth - level (so immediate parent is totalDepth-1, grandparent is totalDepth-2, etc.)
|
|
191
|
+
// Actually we want: root ancestor = 1, next = 2, ..., immediate parent = totalDepth-1, current = totalDepth
|
|
192
|
+
// So breadcrumbLevel = totalDepth - level
|
|
193
|
+
if (breadcrumb) {
|
|
194
|
+
const breadcrumbLevel = totalDepth - level
|
|
195
|
+
setBreadcrumbEntity(data, breadcrumbLevel)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
currentConfig = currentConfig.parent
|
|
200
|
+
level++
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
parentChain.value = newChain
|
|
204
|
+
} catch (err) {
|
|
205
|
+
console.warn('[useEntityItemPage] Failed to load parent chain:', err)
|
|
206
|
+
} finally {
|
|
207
|
+
parentLoading.value = false
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Legacy alias for backwards compatibility
|
|
212
|
+
const loadParent = loadParentChain
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get initial data with parent foreignKey auto-populated
|
|
216
|
+
* Useful for form pages creating child entities
|
|
217
|
+
*/
|
|
218
|
+
function getInitialDataWithParent() {
|
|
219
|
+
const baseData = manager.getInitialData()
|
|
220
|
+
|
|
221
|
+
if (!parentConfig.value || !parentId.value) {
|
|
222
|
+
return baseData
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Auto-populate foreignKey with parent ID
|
|
226
|
+
return {
|
|
227
|
+
...baseData,
|
|
228
|
+
[parentConfig.value.foreignKey]: parentId.value
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
85
232
|
// ============ ID EXTRACTION ============
|
|
86
233
|
|
|
87
234
|
/**
|
|
@@ -121,8 +268,10 @@ export function useEntityItemPage(config = {}) {
|
|
|
121
268
|
data.value = transformed
|
|
122
269
|
|
|
123
270
|
// Share with navigation context for breadcrumb
|
|
271
|
+
// Level = depth of chain (1 if no parent, 2 if 1 parent, 3 if 2 parents, etc.)
|
|
124
272
|
if (breadcrumb) {
|
|
125
|
-
|
|
273
|
+
const level = getChainDepth()
|
|
274
|
+
setBreadcrumbEntity(transformed, level)
|
|
126
275
|
}
|
|
127
276
|
|
|
128
277
|
if (onLoadSuccess) {
|
|
@@ -168,13 +317,17 @@ export function useEntityItemPage(config = {}) {
|
|
|
168
317
|
|
|
169
318
|
// ============ LIFECYCLE ============
|
|
170
319
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
320
|
+
onMounted(async () => {
|
|
321
|
+
// Auto-load parent entity if configured
|
|
322
|
+
if (autoLoadParent && parentConfig.value && parentId.value) {
|
|
323
|
+
await loadParent()
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Load current entity
|
|
327
|
+
if (loadOnMount && entityId.value) {
|
|
328
|
+
load()
|
|
329
|
+
}
|
|
330
|
+
})
|
|
178
331
|
|
|
179
332
|
// ============ RETURN ============
|
|
180
333
|
|
|
@@ -189,6 +342,18 @@ export function useEntityItemPage(config = {}) {
|
|
|
189
342
|
entityLabel,
|
|
190
343
|
isLoaded,
|
|
191
344
|
|
|
345
|
+
// Parent chain (supports N-level nesting)
|
|
346
|
+
parentConfig,
|
|
347
|
+
parentId,
|
|
348
|
+
parentData, // Immediate parent (level 1)
|
|
349
|
+
parentChain, // All parents: Map(level -> data)
|
|
350
|
+
parentLoading,
|
|
351
|
+
loadParent, // Alias for loadParentChain
|
|
352
|
+
loadParentChain,
|
|
353
|
+
getParentManager,
|
|
354
|
+
getChainDepth,
|
|
355
|
+
getInitialDataWithParent,
|
|
356
|
+
|
|
192
357
|
// Actions
|
|
193
358
|
load,
|
|
194
359
|
reload,
|
|
@@ -321,13 +321,37 @@ export function useListPageBuilder(config = {}) {
|
|
|
321
321
|
/**
|
|
322
322
|
* Add standard "Create" header action
|
|
323
323
|
* Respects manager.canCreate() for visibility
|
|
324
|
+
*
|
|
325
|
+
* @param {string|object} labelOrOptions - Label string or options object
|
|
326
|
+
* @param {string} labelOrOptions.label - Button label
|
|
327
|
+
* @param {string} labelOrOptions.routeName - Custom route name (overrides auto-detection)
|
|
328
|
+
* @param {object} labelOrOptions.params - Custom route params (merged with current params)
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* // Simple usage
|
|
332
|
+
* list.addCreateAction('New Book')
|
|
333
|
+
*
|
|
334
|
+
* // With custom route (for child entities or special cases)
|
|
335
|
+
* list.addCreateAction({ label: 'New Command', routeName: 'bot-command-create' })
|
|
324
336
|
*/
|
|
325
|
-
function addCreateAction(
|
|
326
|
-
const
|
|
337
|
+
function addCreateAction(labelOrOptions = null) {
|
|
338
|
+
const options = typeof labelOrOptions === 'string'
|
|
339
|
+
? { label: labelOrOptions }
|
|
340
|
+
: (labelOrOptions || {})
|
|
341
|
+
|
|
342
|
+
const createLabel = options.label || `Create ${entityName.charAt(0).toUpperCase() + entityName.slice(1)}`
|
|
343
|
+
|
|
344
|
+
const onClick = options.routeName
|
|
345
|
+
? () => router.push({
|
|
346
|
+
name: options.routeName,
|
|
347
|
+
params: { ...route.params, ...options.params }
|
|
348
|
+
})
|
|
349
|
+
: goToCreate
|
|
350
|
+
|
|
327
351
|
addHeaderAction('create', {
|
|
328
352
|
label: createLabel,
|
|
329
353
|
icon: 'pi pi-plus',
|
|
330
|
-
onClick
|
|
354
|
+
onClick,
|
|
331
355
|
visible: () => manager.canCreate()
|
|
332
356
|
})
|
|
333
357
|
}
|
|
@@ -1128,8 +1152,45 @@ export function useListPageBuilder(config = {}) {
|
|
|
1128
1152
|
}
|
|
1129
1153
|
|
|
1130
1154
|
// ============ NAVIGATION ============
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Find create route for current context
|
|
1158
|
+
* For child routes (with parent config), looks for sibling create route
|
|
1159
|
+
* Falls back to standard {routePrefix}-create
|
|
1160
|
+
*/
|
|
1161
|
+
function findCreateRoute() {
|
|
1162
|
+
const parentConfig = route.meta?.parent
|
|
1163
|
+
|
|
1164
|
+
if (parentConfig) {
|
|
1165
|
+
// Child route: find create route by path pattern
|
|
1166
|
+
// Current matched route path: /bots/:id/commands → look for /bots/:id/commands/create
|
|
1167
|
+
const currentMatched = route.matched[route.matched.length - 1]
|
|
1168
|
+
if (currentMatched) {
|
|
1169
|
+
const createPath = `${currentMatched.path}/create`
|
|
1170
|
+
const createRoute = router.getRoutes().find(r => r.path === createPath)
|
|
1171
|
+
if (createRoute?.name) {
|
|
1172
|
+
return { name: createRoute.name, params: route.params }
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Fallback: try common naming pattern (list-name → singular-create)
|
|
1177
|
+
// e.g., bot-commands → bot-command-create
|
|
1178
|
+
const currentName = route.name
|
|
1179
|
+
if (currentName && currentName.endsWith('s')) {
|
|
1180
|
+
const singularName = currentName.slice(0, -1)
|
|
1181
|
+
const createRouteName = `${singularName}-create`
|
|
1182
|
+
if (router.hasRoute(createRouteName)) {
|
|
1183
|
+
return { name: createRouteName, params: route.params }
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// Default: standard entity route
|
|
1189
|
+
return { name: `${routePrefix}-create` }
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1131
1192
|
function goToCreate() {
|
|
1132
|
-
router.push(
|
|
1193
|
+
router.push(findCreateRoute())
|
|
1133
1194
|
}
|
|
1134
1195
|
|
|
1135
1196
|
function goToEdit(item) {
|