diff --git a/app/components/SkeletonComponentNodeSelector.vue b/app/components/SkeletonComponentNodeSelector.vue
new file mode 100644
index 0000000..2843a88
--- /dev/null
+++ b/app/components/SkeletonComponentNodeSelector.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+ {{ node.name || node.typeComposantLabel || 'Composant' }}
+
+
+ {{
+ node.typeComposantLabel
+ ? `Famille : ${node.typeComposantLabel}`
+ : node.typeComposantId
+ ? `Famille : ${node.typeComposantId}`
+ : 'Famille non définie'
+ }}
+
+
+
+
+ Modèle : {{ selectedComponentModelLabel }}
+
+
+
+
+
+
+
+
Pièces associées
+
+
+
{{ piece.name || piece.typePieceLabel || 'Pièce' }}
+
+ {{
+ piece.typePieceLabel
+ ? `Famille : ${piece.typePieceLabel}`
+ : piece.typePieceId
+ ? `Famille : ${piece.typePieceId}`
+ : 'Famille non définie'
+ }}
+
+
+
+
+
+
+
+
Sous-composants
+
+
+
+
+
+
+
diff --git a/app/pages/machine/[id].vue b/app/pages/machine/[id].vue
index 3236583..7b05ca0 100644
--- a/app/pages/machine/[id].vue
+++ b/app/pages/machine/[id].vue
@@ -585,33 +585,46 @@
-
-
@@ -1125,6 +1138,33 @@ const normalizeComponentNode = (source, context = {}) => {
return node
}
+const gatherTypeIdsFromComponentNode = (node, componentTypeIds, pieceTypeIds) => {
+ if (!node || typeof node !== 'object') {
+ return
+ }
+
+ if (node.typeComposantId) {
+ componentTypeIds.add(node.typeComposantId)
+ }
+
+ if (Array.isArray(node.pieces)) {
+ node.pieces.forEach((piece) => {
+ const typeId = piece?.typePieceId
+ || piece?.typePiece?.id
+ || null
+ if (typeId) {
+ pieceTypeIds.add(typeId)
+ }
+ })
+ }
+
+ if (Array.isArray(node.subComponents)) {
+ node.subComponents.forEach((subNode) => {
+ gatherTypeIdsFromComponentNode(subNode, componentTypeIds, pieceTypeIds)
+ })
+ }
+}
+
const createEmptyComponentDefinition = (requirement) => {
return normalizeComponentNode(
{
@@ -1212,6 +1252,7 @@ const handleNodeComponentModelChange = async (node, typeComposantId, modelId) =>
}
applyDefinitionToNode(node, definition)
node.__componentModelId = selectedModel.id
+ await ensureModelsForNodeDefinition(node)
} else {
node.__componentModelId = null
node.pieces = []
@@ -1226,6 +1267,26 @@ const handleNodePieceModelChange = async (pieceNode, typePieceId, modelId) => {
pieceNode.__pieceModelId = modelId || null
}
+const ensureModelsForNodeDefinition = async (node) => {
+ if (!node) {
+ return
+ }
+ const componentTypeIds = new Set()
+ const pieceTypeIds = new Set()
+ gatherTypeIdsFromComponentNode(node, componentTypeIds, pieceTypeIds)
+
+ const loaders = [
+ ...Array.from(componentTypeIds).filter(Boolean).map((id) => loadComponentModels(id)),
+ ...Array.from(pieceTypeIds).filter(Boolean).map((id) => loadPieceModels(id)),
+ ]
+
+ if (!loaders.length) {
+ return
+ }
+
+ await Promise.allSettled(loaders)
+}
+
const getComponentRequirementEntries = (requirementId) => {
return componentRequirementSelections[requirementId] || []
}
@@ -1319,6 +1380,13 @@ const updateComponentSelectionEntry = (requirementId, index, patch) => {
}
}
+ if (Object.prototype.hasOwnProperty.call(patch, 'name') && updated.definition) {
+ updated.definition = {
+ ...updated.definition,
+ name: patch.name,
+ }
+ }
+
return updated
})
}
@@ -1377,6 +1445,7 @@ const initializeSkeletonRequirementSelections = async () => {
}
const componentTypeIds = new Set()
+ const pieceTypeIds = new Set()
;(type.componentRequirements || []).forEach((requirement) => {
if (requirement.typeComposantId) {
componentTypeIds.add(requirement.typeComposantId)
@@ -1385,20 +1454,30 @@ const initializeSkeletonRequirementSelections = async () => {
(component) => component.typeMachineComponentRequirementId === requirement.id
)
const entries = existingComponents.map((component) => {
+ const definition = buildDefinitionFromComponent(component, requirement)
+ gatherTypeIdsFromComponentNode(definition, componentTypeIds, pieceTypeIds)
const modelId = component.composantModelId || component.composantModel?.id || null
if (modelId) {
- return { mode: 'model', componentModelId: modelId, name: '' }
+ return { mode: 'model', componentModelId: modelId, name: '', definition }
+ }
+ return {
+ mode: 'manual',
+ componentModelId: '',
+ name: component.name || '',
+ definition,
}
- return { mode: 'manual', componentModelId: '', name: component.name || '' }
})
const min = requirement.minCount ?? (requirement.required ? 1 : 0)
while (entries.length < min) {
- entries.push(createComponentSelectionEntry())
+ const entry = createComponentSelectionEntry(requirement)
+ if (entry.definition) {
+ gatherTypeIdsFromComponentNode(entry.definition, componentTypeIds, pieceTypeIds)
+ }
+ entries.push(entry)
}
componentRequirementSelections[requirement.id] = entries.length ? entries : []
})
- const pieceTypeIds = new Set()
const allPieces = collectPiecesForSkeleton()
;(type.pieceRequirements || []).forEach((requirement) => {
if (requirement.typePieceId) {
@@ -1433,6 +1512,19 @@ const initializeSkeletonRequirementSelections = async () => {
}
}
+const handleRequirementComponentModelChange = async (requirement, entryIndex, value) => {
+ if (!requirement) {
+ return
+ }
+ updateComponentSelectionEntry(requirement.id, entryIndex, { componentModelId: value })
+ await nextTick()
+ const entries = getComponentRequirementEntries(requirement.id)
+ const entry = entries[entryIndex]
+ if (value && entry?.definition) {
+ await ensureModelsForNodeDefinition(entry.definition)
+ }
+}
+
const openSkeletonEditor = async () => {
if (skeletonEditor.open) {
return
@@ -1520,16 +1612,31 @@ const validateSkeletonSelections = (type) => {
usableEntries.forEach((entry) => {
if (entry.mode === 'model') {
- componentSelectionsPayload.push({
+ const definitionPayload = serializeComponentNodeDefinition(entry.definition)
+ const payload = {
requirementId: requirement.id,
componentModelId: entry.componentModelId,
- })
+ }
+ if (
+ definitionPayload &&
+ ((definitionPayload.pieces && definitionPayload.pieces.length) ||
+ (definitionPayload.subComponents && definitionPayload.subComponents.length) ||
+ (definitionPayload.customFields && definitionPayload.customFields.length))
+ ) {
+ payload.definition = definitionPayload
+ }
+ componentSelectionsPayload.push(payload)
} else {
+ const manualName = typeof entry.name === 'string' ? entry.name.trim() : ''
+ const definitionPayload = serializeComponentNodeDefinition(entry.definition) || {}
+ if (manualName && !definitionPayload.name) {
+ definitionPayload.name = manualName
+ }
componentSelectionsPayload.push({
requirementId: requirement.id,
- definition: {
- name: entry.name.trim(),
- },
+ definition: Object.keys(definitionPayload).length
+ ? definitionPayload
+ : { name: manualName || requirement.typeComposant?.name || 'Composant' },
})
}
})
@@ -1573,6 +1680,132 @@ const validateSkeletonSelections = (type) => {
}
}
+const parseOptionsText = (text) => {
+ return text
+ .split(/\r?\n/)
+ .map((option) => option.trim())
+ .filter((option) => option.length > 0)
+}
+
+const serializeCustomFieldDefinition = (field) => {
+ if (!field || typeof field !== 'object') {
+ return null
+ }
+ const name = typeof field.name === 'string' ? field.name.trim() : ''
+ if (!name) {
+ return null
+ }
+ const type = typeof field.type === 'string' && field.type.trim().length
+ ? field.type.trim()
+ : 'text'
+ const result = {
+ name,
+ type,
+ required: !!field.required,
+ }
+
+ let options = []
+ if (Array.isArray(field.options) && field.options.length) {
+ options = field.options
+ .map((option) => (typeof option === 'string' ? option.trim() : ''))
+ .filter((option) => option.length > 0)
+ } else if (type === 'select' && typeof field.optionsText === 'string') {
+ options = parseOptionsText(field.optionsText)
+ }
+
+ if (options.length) {
+ result.options = options
+ }
+
+ return result
+}
+
+const serializePieceNodeDefinition = (piece) => {
+ if (!piece || typeof piece !== 'object') {
+ return null
+ }
+ const payload = {}
+ const name = typeof piece.name === 'string' ? piece.name.trim() : ''
+ if (name) {
+ payload.name = name
+ }
+ const typePieceId = piece.typePieceId || piece.typePiece?.id || ''
+ if (typePieceId) {
+ payload.typePieceId = typePieceId
+ }
+ const typePieceLabel = typeof piece.typePieceLabel === 'string' ? piece.typePieceLabel.trim() : ''
+ if (typePieceLabel) {
+ payload.typePieceLabel = typePieceLabel
+ }
+ if (piece.__pieceModelId) {
+ payload.pieceModelId = piece.__pieceModelId
+ }
+ if (Array.isArray(piece.customFields) && piece.customFields.length) {
+ const customFields = piece.customFields
+ .map((field) => serializeCustomFieldDefinition(field))
+ .filter(Boolean)
+ if (customFields.length) {
+ payload.customFields = customFields
+ }
+ }
+ return Object.keys(payload).length > 0 ? payload : null
+}
+
+const serializeComponentNodeDefinition = (node) => {
+ if (!node || typeof node !== 'object') {
+ return null
+ }
+
+ const payload = {}
+ const name = typeof node.name === 'string' ? node.name.trim() : ''
+ if (name) {
+ payload.name = name
+ }
+ const description = typeof node.description === 'string' ? node.description.trim() : ''
+ if (description) {
+ payload.description = description
+ }
+ const typeComposantId = node.typeComposantId || node.typeComposant?.id || ''
+ if (typeComposantId) {
+ payload.typeComposantId = typeComposantId
+ }
+ const typeComposantLabel = typeof node.typeComposantLabel === 'string'
+ ? node.typeComposantLabel.trim()
+ : ''
+ if (typeComposantLabel) {
+ payload.typeComposantLabel = typeComposantLabel
+ }
+ if (node.__componentModelId) {
+ payload.componentModelId = node.__componentModelId
+ }
+ if (Array.isArray(node.customFields) && node.customFields.length) {
+ const customFields = node.customFields
+ .map((field) => serializeCustomFieldDefinition(field))
+ .filter(Boolean)
+ if (customFields.length) {
+ payload.customFields = customFields
+ }
+ }
+ if (Array.isArray(node.pieces) && node.pieces.length) {
+ const pieces = node.pieces
+ .map((piece) => serializePieceNodeDefinition(piece))
+ .filter(Boolean)
+ if (pieces.length) {
+ payload.pieces = pieces
+ }
+ }
+ if (Array.isArray(node.subComponents) && node.subComponents.length) {
+ const subComponents = node.subComponents
+ .map((sub) => serializeComponentNodeDefinition(sub))
+ .filter(Boolean)
+ if (subComponents.length) {
+ payload.subComponents = subComponents
+ }
+ }
+
+ return Object.keys(payload).length > 0 ? payload : null
+}
+
const applySkeletonReconfigurationResult = async (data) => {
if (!data) return