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 @@ + + + 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 @@ -
-
- - - {{ model.name }} - - -

Chargement des modèles...

-

- Aucun modèle disponible pour ce type. -

+ + + +

Chargement des modèles...

+

+ Aucun modèle disponible pour ce type. +

+
+
@@ -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