chore: update frontend configuration
This commit is contained in:
@@ -70,52 +70,43 @@
|
||||
>
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex-1 space-y-3">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-2">
|
||||
<input
|
||||
v-model="piece.name"
|
||||
type="text"
|
||||
class="input input-bordered input-xs"
|
||||
placeholder="Nom de la pièce"
|
||||
/>
|
||||
<input
|
||||
v-model="piece.reference"
|
||||
type="text"
|
||||
class="input input-bordered input-xs"
|
||||
placeholder="Référence"
|
||||
/>
|
||||
<input
|
||||
v-model.number="piece.quantity"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input input-bordered input-xs"
|
||||
placeholder="Quantité"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Famille de pièce</span></label>
|
||||
<div>
|
||||
<input
|
||||
:list="`component-piece-type-options-${index}`"
|
||||
v-model="piece.typePieceLabel"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="input input-bordered input-xs"
|
||||
placeholder="Rechercher une famille"
|
||||
@change="handlePieceTypeChange(piece)"
|
||||
@blur="handlePieceTypeChange(piece)"
|
||||
/>
|
||||
<datalist :id="`component-piece-type-options-${index}`">
|
||||
<option
|
||||
v-for="type in availablePieceTypes"
|
||||
:key="type.id"
|
||||
:value="formatPieceTypeOption(type)"
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Famille de pièce</span></label>
|
||||
<div>
|
||||
<input
|
||||
:list="`component-piece-type-options-${index}`"
|
||||
v-model="piece.typePieceLabel"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
class="input input-bordered input-xs"
|
||||
placeholder="Sélectionner une famille"
|
||||
@change="handlePieceTypeChange(piece)"
|
||||
@blur="handlePieceTypeChange(piece)"
|
||||
/>
|
||||
</datalist>
|
||||
<datalist :id="`component-piece-type-options-${index}`">
|
||||
<option
|
||||
v-for="type in availablePieceTypes"
|
||||
:key="type.id"
|
||||
:value="formatPieceTypeOption(type)"
|
||||
/>
|
||||
</datalist>
|
||||
</div>
|
||||
<p class="mt-1 text-[11px] text-gray-500">
|
||||
{{ piece.typePieceId ? `Sélection : ${getPieceTypeLabel(piece.typePieceId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Quantité (optionnel)</span></label>
|
||||
<input
|
||||
v-model.number="piece.quantity"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
class="input input-bordered input-xs"
|
||||
placeholder="Quantité"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 text-[11px] text-gray-500">
|
||||
{{ piece.typePieceId ? `Sélection : ${getPieceTypeLabel(piece.typePieceId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-error btn-xs btn-square" @click="removePiece(index)">
|
||||
@@ -144,6 +135,7 @@
|
||||
:node="subComponent"
|
||||
:depth="0"
|
||||
:piece-types="availablePieceTypes"
|
||||
:component-types="availableComponentTypes"
|
||||
@remove="removeSubComponent(index)"
|
||||
/>
|
||||
</div>
|
||||
@@ -162,6 +154,7 @@ import {
|
||||
normalizeStructureForSave,
|
||||
} from '~/shared/modelUtils'
|
||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||
|
||||
defineOptions({ name: 'ComponentModelStructureEditor' })
|
||||
|
||||
@@ -207,18 +200,25 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
type PieceTypeOption = {
|
||||
type ModelTypeOption = {
|
||||
id: string
|
||||
name: string
|
||||
code?: string | null
|
||||
}
|
||||
|
||||
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => {
|
||||
if (!type) return ''
|
||||
return type.code ? `${type.name} (${type.code})` : type.name
|
||||
}
|
||||
|
||||
const availablePieceTypes = computed<PieceTypeOption[]>(() => pieceTypes.value ?? [])
|
||||
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
||||
|
||||
const availablePieceTypes = computed<ModelTypeOption[]>(() => pieceTypes.value ?? [])
|
||||
const availableComponentTypes = computed<ModelTypeOption[]>(() => componentTypes.value ?? [])
|
||||
|
||||
const pieceTypeMap = computed(() => {
|
||||
const map = new Map<string, PieceTypeOption>()
|
||||
const map = new Map<string, ModelTypeOption>()
|
||||
availablePieceTypes.value.forEach((type) => {
|
||||
if (type && typeof type.id === 'string') {
|
||||
map.set(type.id, type)
|
||||
@@ -227,10 +227,18 @@ const pieceTypeMap = computed(() => {
|
||||
return map
|
||||
})
|
||||
|
||||
const formatPieceTypeOption = (type: PieceTypeOption | undefined | null) => {
|
||||
if (!type) return ''
|
||||
return type.code ? `${type.name} (${type.code})` : type.name
|
||||
}
|
||||
const componentTypeMap = computed(() => {
|
||||
const map = new Map<string, ModelTypeOption>()
|
||||
availableComponentTypes.value.forEach((type) => {
|
||||
if (type && typeof type.id === 'string') {
|
||||
map.set(type.id, type)
|
||||
}
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
const formatPieceTypeOption = (type: ModelTypeOption | undefined | null) => formatModelTypeOption(type)
|
||||
const formatComponentTypeOption = (type: ModelTypeOption | undefined | null) => formatModelTypeOption(type)
|
||||
|
||||
const resolvePieceType = (input: string) => {
|
||||
const normalized = input.trim().toLowerCase()
|
||||
@@ -251,6 +259,25 @@ const resolvePieceType = (input: string) => {
|
||||
)
|
||||
}
|
||||
|
||||
const resolveComponentType = (input: string) => {
|
||||
const normalized = input.trim().toLowerCase()
|
||||
if (!normalized) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
availableComponentTypes.value.find((type) => {
|
||||
const formatted = formatComponentTypeOption(type).toLowerCase()
|
||||
const name = (type?.name ?? '').toLowerCase()
|
||||
const code = (type?.code ?? '').toLowerCase()
|
||||
return (
|
||||
formatted === normalized
|
||||
|| name === normalized
|
||||
|| (!!code && code === normalized)
|
||||
)
|
||||
}) ?? null
|
||||
)
|
||||
}
|
||||
|
||||
const getPieceTypeLabel = (id?: string) => {
|
||||
if (!id) return ''
|
||||
const option = pieceTypeMap.value.get(id)
|
||||
@@ -263,12 +290,21 @@ const updatePieceTypeLabel = (piece: any) => {
|
||||
}
|
||||
if (piece.typePieceId) {
|
||||
const option = pieceTypeMap.value.get(piece.typePieceId)
|
||||
piece.typePieceLabel = option ? formatPieceTypeOption(option) : piece.typePieceLabel || ''
|
||||
if (option) {
|
||||
piece.typePieceLabel = formatPieceTypeOption(option)
|
||||
piece.name = option.name || formatPieceTypeOption(option)
|
||||
} else if (!piece.typePieceLabel) {
|
||||
piece.name = ''
|
||||
}
|
||||
} else if (piece.typePieceLabel) {
|
||||
const match = resolvePieceType(piece.typePieceLabel)
|
||||
if (match) {
|
||||
piece.typePieceId = match.id
|
||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
||||
piece.name = match.name || formatPieceTypeOption(match)
|
||||
} else {
|
||||
piece.typePieceLabel = ''
|
||||
piece.name = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,14 +317,18 @@ const handlePieceTypeChange = (piece: any) => {
|
||||
if (!value) {
|
||||
piece.typePieceId = ''
|
||||
piece.typePieceLabel = ''
|
||||
piece.name = ''
|
||||
return
|
||||
}
|
||||
const match = resolvePieceType(value)
|
||||
if (match) {
|
||||
piece.typePieceId = match.id
|
||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
||||
piece.name = match.name || formatPieceTypeOption(match)
|
||||
} else {
|
||||
piece.typePieceId = ''
|
||||
piece.typePieceLabel = ''
|
||||
piece.name = ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,37 +344,84 @@ const applyPieceLabels = (pieces?: any[]) => {
|
||||
if (match) {
|
||||
piece.typePieceId = match.id
|
||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
||||
piece.name = match.name || formatPieceTypeOption(match)
|
||||
} else {
|
||||
piece.typePieceLabel = ''
|
||||
piece.name = ''
|
||||
}
|
||||
} else if (!piece?.name) {
|
||||
piece.name = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const applyComponentTypeLabel = (component: any) => {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
if (component.typeComposantId) {
|
||||
const option = componentTypeMap.value.get(component.typeComposantId)
|
||||
if (option) {
|
||||
component.typeComposantLabel = formatComponentTypeOption(option)
|
||||
component.name = option.name || formatComponentTypeOption(option)
|
||||
} else if (!component.typeComposantLabel) {
|
||||
component.name = ''
|
||||
}
|
||||
} else if (component.typeComposantLabel) {
|
||||
const match = resolveComponentType(component.typeComposantLabel)
|
||||
if (match) {
|
||||
component.typeComposantId = match.id
|
||||
component.typeComposantLabel = formatComponentTypeOption(match)
|
||||
component.name = match.name || formatComponentTypeOption(match)
|
||||
} else {
|
||||
component.typeComposantLabel = ''
|
||||
component.name = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const traverseSubComponents = (components?: any[]) => {
|
||||
if (!Array.isArray(components)) {
|
||||
return
|
||||
}
|
||||
components.forEach((component) => {
|
||||
applyComponentTypeLabel(component)
|
||||
applyPieceLabels(component?.pieces)
|
||||
traverseSubComponents(component?.subComponents)
|
||||
})
|
||||
}
|
||||
|
||||
const syncAllPieceTypeLabels = () => {
|
||||
const syncAllTypeLabels = () => {
|
||||
applyPieceLabels(localStructure.pieces)
|
||||
traverseSubComponents(localStructure.subComponents)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const loaders: Promise<any>[] = []
|
||||
if (!availablePieceTypes.value.length) {
|
||||
await loadPieceTypes()
|
||||
loaders.push(loadPieceTypes())
|
||||
}
|
||||
syncAllPieceTypeLabels()
|
||||
if (!availableComponentTypes.value.length) {
|
||||
loaders.push(loadComponentTypes())
|
||||
}
|
||||
if (loaders.length) {
|
||||
await Promise.all(loaders)
|
||||
}
|
||||
syncAllTypeLabels()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => availablePieceTypes.value,
|
||||
() => {
|
||||
syncAllPieceTypeLabels()
|
||||
syncAllTypeLabels()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => availableComponentTypes.value,
|
||||
() => {
|
||||
syncAllTypeLabels()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
@@ -365,7 +452,6 @@ const addPiece = () => {
|
||||
ensureArray('pieces')
|
||||
localStructure.pieces.push({
|
||||
name: '',
|
||||
reference: '',
|
||||
quantity: undefined,
|
||||
typePieceId: '',
|
||||
typePieceLabel: '',
|
||||
@@ -383,6 +469,8 @@ const addSubComponent = () => {
|
||||
name: '',
|
||||
description: '',
|
||||
quantity: undefined,
|
||||
typeComposantId: '',
|
||||
typeComposantLabel: '',
|
||||
customFields: [],
|
||||
pieces: [],
|
||||
subComponents: [],
|
||||
|
||||
Reference in New Issue
Block a user