refactor: adopt canonical component model structure schema
This commit is contained in:
@@ -320,13 +320,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Sub Components -->
|
||||
<div v-if="component.subComponents && component.subComponents.length > 0" class="space-y-3">
|
||||
<div v-if="childComponents.length > 0" class="space-y-3">
|
||||
<h4 class="font-semibold text-gray-700">
|
||||
Sous-composants
|
||||
</h4>
|
||||
<div class="space-y-3 pl-4 border-l-2 border-gray-200">
|
||||
<ComponentItem
|
||||
v-for="subComponent in component.subComponents"
|
||||
v-for="subComponent in childComponents"
|
||||
:key="subComponent.id"
|
||||
:component="subComponent"
|
||||
:is-edit-mode="isEditMode"
|
||||
@@ -415,6 +415,10 @@ const componentModelOptionsList = computed(() => {
|
||||
return Array.isArray(provided) && provided.length ? provided : props.componentModelOptions
|
||||
})
|
||||
const pieceModelOptionsList = computed(() => props.pieceModelOptionsProvider(props.component) || [])
|
||||
const childComponents = computed(() => {
|
||||
const list = props.component.subcomponents || props.component.subComponents || []
|
||||
return Array.isArray(list) ? list : []
|
||||
})
|
||||
function fieldKeyFromNameAndType(name, type) {
|
||||
const normalizedName = typeof name === 'string' ? name : ''
|
||||
const normalizedType = typeof type === 'string' ? type : ''
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '~/shared/modelUtils'
|
||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
||||
|
||||
defineOptions({ name: 'ComponentModelStructureEditor' })
|
||||
|
||||
@@ -46,7 +47,7 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const localStructure = reactive(hydrateStructureForEditor(props.modelValue))
|
||||
const localStructure = reactive<ComponentModelStructure>(hydrateStructureForEditor(props.modelValue))
|
||||
const previousLockedLabel = ref(props.rootTypeLabel || '')
|
||||
|
||||
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||
@@ -77,9 +78,14 @@ const syncRootType = () => {
|
||||
localStructure.typeComposantId = newTypeId
|
||||
localStructure.typeComposantLabel = newLabel
|
||||
|
||||
const match = availableComponentTypes.value.find((type) => type?.id === newTypeId)
|
||||
if (match?.code) {
|
||||
localStructure.familyCode = match.code
|
||||
}
|
||||
|
||||
const previousLabel = previousLockedLabel.value
|
||||
if (!localStructure.name || localStructure.name === previousLabel || localStructure.name === '') {
|
||||
localStructure.name = newLabel || localStructure.name
|
||||
if (!localStructure.alias || localStructure.alias === previousLabel || localStructure.alias === '') {
|
||||
localStructure.alias = newLabel || localStructure.alias
|
||||
}
|
||||
|
||||
previousLockedLabel.value = newLabel
|
||||
@@ -91,7 +97,12 @@ const syncFromProps = (value: any) => {
|
||||
const hydrated = hydrateStructureForEditor(value)
|
||||
localStructure.customFields = hydrated.customFields
|
||||
localStructure.pieces = hydrated.pieces
|
||||
localStructure.subComponents = hydrated.subComponents
|
||||
localStructure.subcomponents = hydrated.subcomponents
|
||||
localStructure.typeComposantId = hydrated.typeComposantId
|
||||
localStructure.typeComposantLabel = hydrated.typeComposantLabel
|
||||
localStructure.modelId = hydrated.modelId
|
||||
localStructure.familyCode = hydrated.familyCode
|
||||
localStructure.alias = hydrated.alias
|
||||
lastEmitted = JSON.stringify(cloneStructure(value))
|
||||
syncRootType()
|
||||
}
|
||||
|
||||
@@ -58,6 +58,6 @@ const props = defineProps({
|
||||
selection: { type: Object, required: true }
|
||||
})
|
||||
|
||||
const childComponents = computed(() => props.component.subComponents || [])
|
||||
const childComponents = computed(() => props.component.subcomponents || props.component.subComponents || [])
|
||||
const childPieces = computed(() => props.component.pieces || [])
|
||||
</script>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<div class="flex flex-wrap items-center gap-2 text-sm text-gray-600">
|
||||
<span v-if="stats.customFields" class="badge badge-outline badge-sm">{{ stats.customFields }} champ(s)</span>
|
||||
<span v-if="stats.pieces" class="badge badge-outline badge-sm">{{ stats.pieces }} pièce(s)</span>
|
||||
<span v-if="stats.subComponents" class="badge badge-outline badge-sm">{{ stats.subComponents }} sous-composant(s)</span>
|
||||
<span v-if="!stats.customFields && !stats.pieces && !stats.subComponents" class="text-xs text-gray-500">
|
||||
<span v-if="stats.subcomponents" class="badge badge-outline badge-sm">{{ stats.subcomponents }} sous-composant(s)</span>
|
||||
<span v-if="!stats.customFields && !stats.pieces && !stats.subcomponents" class="text-xs text-gray-500">
|
||||
Structure vide
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="space-y-1">
|
||||
<h4 class="font-semibold text-sm">
|
||||
{{ node.name || node.typeComposantLabel || 'Composant' }}
|
||||
{{ node.alias || node.typeComposantLabel || 'Composant' }}
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{
|
||||
@@ -46,15 +46,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasPieces" class="space-y-2">
|
||||
<h5 class="text-[11px] font-semibold uppercase text-gray-500">Pièces associées</h5>
|
||||
<div
|
||||
v-for="(piece, pieceIndex) in node.pieces"
|
||||
:key="pieceIndex"
|
||||
class="bg-base-200/60 border border-base-200 rounded-md p-3 space-y-2"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<span class="font-medium text-sm">{{ piece.name || piece.typePieceLabel || 'Pièce' }}</span>
|
||||
<div v-if="hasPieces" class="space-y-2">
|
||||
<h5 class="text-[11px] font-semibold uppercase text-gray-500">Pièces associées</h5>
|
||||
<div
|
||||
v-for="(piece, pieceIndex) in node.pieces"
|
||||
:key="pieceIndex"
|
||||
class="bg-base-200/60 border border-base-200 rounded-md p-3 space-y-2"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<span class="font-medium text-sm">{{ piece.typePieceLabel || 'Pièce' }}</span>
|
||||
<p class="text-[11px] text-gray-500">
|
||||
{{
|
||||
piece.typePieceLabel
|
||||
@@ -95,7 +95,7 @@
|
||||
<div v-if="hasSubComponents" class="space-y-3">
|
||||
<h5 class="text-[11px] font-semibold uppercase text-gray-500">Sous-composants</h5>
|
||||
<SkeletonComponentNodeSelector
|
||||
v-for="(subComponent, index) in node.subComponents"
|
||||
v-for="(subComponent, index) in (node.subcomponents || node.subComponents || [])"
|
||||
:key="index"
|
||||
:node="subComponent"
|
||||
:depth="depth + 1"
|
||||
@@ -168,7 +168,10 @@ const selectedComponentModelLabel = computed(() => {
|
||||
})
|
||||
|
||||
const hasPieces = computed(() => Array.isArray(props.node?.pieces) && props.node.pieces.length > 0)
|
||||
const hasSubComponents = computed(() => Array.isArray(props.node?.subComponents) && props.node.subComponents.length > 0)
|
||||
const hasSubComponents = computed(() => {
|
||||
const list = props.node?.subcomponents || props.node?.subComponents || []
|
||||
return Array.isArray(list) && list.length > 0
|
||||
})
|
||||
|
||||
const getPieceModels = (typePieceId) => {
|
||||
if (!typePieceId) {
|
||||
|
||||
@@ -28,6 +28,17 @@
|
||||
<p class="text-[11px] text-gray-500">
|
||||
{{ node.typeComposantId ? `Sélection : ${getComponentTypeLabel(node.typeComposantId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
||||
</p>
|
||||
<div v-if="!isRoot" class="form-control mt-2">
|
||||
<label class="label py-1">
|
||||
<span class="label-text text-[11px]">Alias (optionnel)</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="node.alias"
|
||||
type="text"
|
||||
class="input input-bordered input-xs"
|
||||
placeholder="Alias du sous-composant"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="input input-bordered input-sm bg-base-200 flex items-center">
|
||||
@@ -170,12 +181,12 @@
|
||||
<p v-if="!isRoot" class="text-[11px] text-gray-500">
|
||||
Sélectionnez uniquement la famille de ce sous-composant ; il sera configuré via son propre modèle.
|
||||
</p>
|
||||
<p v-if="!(node.subComponents?.length)" class="text-xs text-gray-500">
|
||||
<p v-if="!(node.subcomponents?.length)" class="text-xs text-gray-500">
|
||||
Aucun sous-composant défini.
|
||||
</p>
|
||||
<div v-else class="space-y-3">
|
||||
<StructureNodeEditor
|
||||
v-for="(subComponent, index) in node.subComponents"
|
||||
v-for="(subComponent, index) in node.subcomponents"
|
||||
:key="`sub-${index}`"
|
||||
:node="subComponent"
|
||||
:depth="depth + 1"
|
||||
@@ -194,6 +205,7 @@
|
||||
import { computed, watch } from 'vue'
|
||||
import IconLucidePlus from '~icons/lucide/plus'
|
||||
import IconLucideTrash from '~icons/lucide/trash'
|
||||
import type { ComponentModelPiece, ComponentModelStructureNode } from '~/shared/types/inventory'
|
||||
|
||||
defineOptions({ name: 'StructureNodeEditor' })
|
||||
|
||||
@@ -203,8 +215,13 @@ type ModelTypeOption = {
|
||||
code?: string | null
|
||||
}
|
||||
|
||||
type EditableStructureNode = ComponentModelStructureNode & {
|
||||
customFields?: any[]
|
||||
pieces?: ComponentModelPiece[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
node: Record<string, any>
|
||||
node: EditableStructureNode
|
||||
depth?: number
|
||||
componentTypes?: ModelTypeOption[]
|
||||
pieceTypes?: ModelTypeOption[]
|
||||
@@ -281,23 +298,31 @@ const formatComponentTypeOption = (type: ModelTypeOption | undefined | null) =>
|
||||
const formatPieceTypeOption = (type: ModelTypeOption | undefined | null) =>
|
||||
formatModelTypeOption(type)
|
||||
|
||||
const ensureArray = (key: 'customFields' | 'pieces' | 'subComponents') => {
|
||||
if (!Array.isArray(props.node[key])) {
|
||||
props.node[key] = []
|
||||
const ensureArray = (key: 'customFields' | 'pieces' | 'subcomponents') => {
|
||||
if (!Array.isArray((props.node as any)[key])) {
|
||||
if (key === 'subcomponents') {
|
||||
props.node.subcomponents = []
|
||||
} else {
|
||||
(props.node as any)[key] = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const syncComponentType = (component: any) => {
|
||||
const syncComponentType = (component: EditableStructureNode) => {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
if (props.lockType && props.isRoot) {
|
||||
if (props.lockedTypeLabel) {
|
||||
component.typeComposantLabel = props.lockedTypeLabel
|
||||
if (!component.name || component.name === component.typeComposantLabel) {
|
||||
component.name = props.lockedTypeLabel
|
||||
if (!component.alias || component.alias === component.typeComposantLabel) {
|
||||
component.alias = props.lockedTypeLabel
|
||||
}
|
||||
}
|
||||
if (component.typeComposantId) {
|
||||
const option = componentTypeMap.value.get(component.typeComposantId)
|
||||
component.familyCode = option?.code ?? component.familyCode
|
||||
}
|
||||
return
|
||||
}
|
||||
const id = typeof component.typeComposantId === 'string'
|
||||
@@ -306,29 +331,31 @@ const syncComponentType = (component: any) => {
|
||||
|
||||
if (!id) {
|
||||
component.typeComposantLabel = ''
|
||||
component.name = ''
|
||||
component.familyCode = ''
|
||||
return
|
||||
}
|
||||
|
||||
const option = componentTypeMap.value.get(id)
|
||||
if (!option) {
|
||||
component.typeComposantLabel = ''
|
||||
component.name = ''
|
||||
component.familyCode = ''
|
||||
return
|
||||
}
|
||||
|
||||
component.typeComposantLabel = formatModelTypeOption(option)
|
||||
component.name = option.name || component.typeComposantLabel
|
||||
component.familyCode = option.code ?? component.familyCode
|
||||
if (!component.alias || component.alias === '' || component.alias === lockedTypeDisplay.value) {
|
||||
component.alias = option.name || component.typeComposantLabel
|
||||
}
|
||||
}
|
||||
|
||||
const updatePieceTypeLabel = (piece: any) => {
|
||||
const updatePieceTypeLabel = (piece: ComponentModelPiece & Record<string, any>) => {
|
||||
if (!piece) return
|
||||
|
||||
if (piece.typePieceId) {
|
||||
const option = pieceTypeMap.value.get(piece.typePieceId)
|
||||
if (option) {
|
||||
piece.typePieceLabel = formatPieceTypeOption(option)
|
||||
piece.name = option.name || formatPieceTypeOption(option)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -345,15 +372,10 @@ const updatePieceTypeLabel = (piece: any) => {
|
||||
if (match) {
|
||||
piece.typePieceId = match.id
|
||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
||||
piece.name = match.name || formatPieceTypeOption(match)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!piece.name) {
|
||||
piece.name = piece.typePieceLabel || ''
|
||||
}
|
||||
}
|
||||
|
||||
const syncPieceLabels = (pieces?: any[]) => {
|
||||
@@ -369,25 +391,22 @@ const handleComponentTypeSelect = (component: any) => {
|
||||
syncComponentType(component)
|
||||
}
|
||||
|
||||
const handlePieceTypeSelect = (piece: any) => {
|
||||
const handlePieceTypeSelect = (piece: ComponentModelPiece & Record<string, any>) => {
|
||||
if (!piece) {
|
||||
return
|
||||
}
|
||||
const id = typeof piece.typePieceId === 'string' ? piece.typePieceId : ''
|
||||
if (!id) {
|
||||
piece.typePieceLabel = ''
|
||||
piece.name = ''
|
||||
return
|
||||
}
|
||||
const option = pieceTypeMap.value.get(id)
|
||||
if (!option) {
|
||||
piece.typePieceId = ''
|
||||
piece.typePieceLabel = ''
|
||||
piece.name = ''
|
||||
return
|
||||
}
|
||||
piece.typePieceLabel = formatPieceTypeOption(option)
|
||||
piece.name = option.name || piece.typePieceLabel
|
||||
}
|
||||
|
||||
const addCustomField = () => {
|
||||
@@ -409,9 +428,9 @@ const removeCustomField = (index: number) => {
|
||||
const addPiece = () => {
|
||||
ensureArray('pieces')
|
||||
props.node.pieces.push({
|
||||
name: '',
|
||||
typePieceId: '',
|
||||
typePieceLabel: '',
|
||||
reference: '',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -421,17 +440,20 @@ const removePiece = (index: number) => {
|
||||
}
|
||||
|
||||
const addSubComponent = () => {
|
||||
ensureArray('subComponents')
|
||||
props.node.subComponents.push({
|
||||
name: '',
|
||||
ensureArray('subcomponents')
|
||||
props.node.subcomponents.push({
|
||||
typeComposantId: '',
|
||||
typeComposantLabel: '',
|
||||
modelId: '',
|
||||
familyCode: '',
|
||||
alias: '',
|
||||
subcomponents: [],
|
||||
})
|
||||
}
|
||||
|
||||
const removeSubComponent = (index: number) => {
|
||||
if (!Array.isArray(props.node.subComponents)) return
|
||||
props.node.subComponents.splice(index, 1)
|
||||
if (!Array.isArray(props.node.subcomponents)) return
|
||||
props.node.subcomponents.splice(index, 1)
|
||||
}
|
||||
|
||||
watch(componentTypes, () => {
|
||||
@@ -463,8 +485,12 @@ watch(
|
||||
if (props.lockType && props.isRoot) {
|
||||
const label = props.lockedTypeLabel || lockedTypeDisplay.value
|
||||
props.node.typeComposantLabel = label
|
||||
if (label && (!props.node.name || props.node.name === lockedTypeDisplay.value)) {
|
||||
props.node.name = label
|
||||
if (label && (!props.node.alias || props.node.alias === lockedTypeDisplay.value)) {
|
||||
props.node.alias = label
|
||||
}
|
||||
if (props.node.typeComposantId) {
|
||||
const option = componentTypeMap.value.get(props.node.typeComposantId)
|
||||
props.node.familyCode = option?.code ?? props.node.familyCode
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user