chore: update frontend configuration

This commit is contained in:
Matthieu
2025-09-26 11:29:47 +02:00
parent b7caa4f552
commit a78938a4d1
64 changed files with 5790 additions and 5129 deletions

View File

@@ -10,12 +10,28 @@
aria-hidden="true"
/>
</button>
<input
v-model="node.name"
type="text"
class="input input-sm input-bordered w-full"
placeholder="Nom du sous-composant"
/>
<div class="flex-1">
<input
:list="componentTypeListId"
v-model="node.typeComposantLabel"
type="search"
autocomplete="off"
class="input input-sm input-bordered w-full"
placeholder="Sélectionner une famille de composant"
@change="handleComponentTypeChange(node)"
@blur="handleComponentTypeChange(node)"
/>
<datalist :id="componentTypeListId">
<option
v-for="type in componentTypes"
:key="type.id"
:value="formatComponentTypeOption(type)"
/>
</datalist>
<p class="mt-1 text-[11px] text-gray-500">
{{ node.typeComposantId ? `Sélection : ${getComponentTypeLabel(node.typeComposantId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
</p>
</div>
</div>
<span v-if="!expanded && node.description" class="text-xs text-gray-500 truncate">
{{ node.description }}
@@ -112,52 +128,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="getPieceTypeListId(pieceIndex)"
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="getPieceTypeListId(pieceIndex)">
<option
v-for="type in pieceTypes"
: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="getPieceTypeListId(pieceIndex)"
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="getPieceTypeListId(pieceIndex)">
<option
v-for="type in pieceTypes"
: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(pieceIndex)">
@@ -188,6 +195,7 @@
:node="sub"
:depth="depth + 1"
:piece-types="pieceTypes"
:component-types="componentTypes"
@remove="removeSubComponent(index)"
/>
</div>
@@ -197,14 +205,14 @@
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { computed, ref, watch, getCurrentInstance } from 'vue'
import IconLucideChevronRight from '~icons/lucide/chevron-right'
import IconLucidePlus from '~icons/lucide/plus'
import IconLucideTrash from '~icons/lucide/trash'
defineOptions({ name: 'StructureSubComponentEditor' })
type PieceTypeOption = {
type ModelTypeOption = {
id: string
name: string
code?: string | null
@@ -213,17 +221,30 @@ type PieceTypeOption = {
const props = withDefaults(defineProps<{
node: Record<string, any>
depth?: number
pieceTypes?: PieceTypeOption[]
pieceTypes?: ModelTypeOption[]
componentTypes?: ModelTypeOption[]
}>(), {
depth: 0,
pieceTypes: () => [],
componentTypes: () => [],
})
const emit = defineEmits(['remove'])
const pieceTypes = computed(() => props.pieceTypes ?? [])
const componentTypes = computed(() => props.componentTypes ?? [])
const instance = getCurrentInstance()
const componentTypeListId = `sub-component-type-options-${instance?.uid ?? 0}`
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => {
if (!type) return ''
return type.code ? `${type.name} (${type.code})` : type.name
}
const pieceTypeMap = computed(() => {
const map = new Map<string, PieceTypeOption>()
;(props.pieceTypes ?? []).forEach((type) => {
const map = new Map<string, ModelTypeOption>()
pieceTypes.value.forEach((type) => {
if (type && typeof type.id === 'string') {
map.set(type.id, type)
}
@@ -231,10 +252,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>()
componentTypes.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()
@@ -242,7 +271,7 @@ const resolvePieceType = (input: string) => {
return null
}
return (
(props.pieceTypes ?? []).find((type) => {
pieceTypes.value.find((type) => {
const formatted = formatPieceTypeOption(type).toLowerCase()
const name = (type?.name ?? '').toLowerCase()
const code = (type?.code ?? '').toLowerCase()
@@ -255,24 +284,58 @@ const resolvePieceType = (input: string) => {
)
}
const resolveComponentType = (input: string) => {
const normalized = input.trim().toLowerCase()
if (!normalized) {
return null
}
return (
componentTypes.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)
return formatPieceTypeOption(option)
}
const getComponentTypeLabel = (id?: string) => {
if (!id) return ''
const option = componentTypeMap.value.get(id)
return formatComponentTypeOption(option)
}
const updatePieceTypeLabel = (piece: any) => {
if (!piece) {
return
}
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 = ''
}
}
}
@@ -285,14 +348,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 = ''
}
}
@@ -310,38 +377,96 @@ 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 handleComponentTypeChange = (component: any) => {
if (!component) {
return
}
const value = typeof component.typeComposantLabel === 'string'
? component.typeComposantLabel.trim()
: ''
if (!value) {
component.typeComposantId = ''
component.typeComposantLabel = ''
component.name = ''
return
}
const match = resolveComponentType(value)
if (match) {
component.typeComposantId = match.id
component.typeComposantLabel = formatComponentTypeOption(match)
component.name = match.name || formatComponentTypeOption(match)
} else {
component.typeComposantId = ''
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 syncPieceTypeLabels = () => {
const syncTypeLabels = () => {
applyComponentTypeLabel(props.node)
applyPieceLabels(props.node?.pieces)
traverseSubComponents(props.node?.subComponents)
}
watch(
() => props.pieceTypes,
() => {
syncPieceTypeLabels()
},
{ deep: true, immediate: true }
)
watch(pieceTypes, () => {
syncTypeLabels()
}, { deep: true, immediate: true })
watch(componentTypes, () => {
syncTypeLabels()
}, { deep: true, immediate: true })
watch(
() => props.node,
() => {
syncPieceTypeLabels()
syncTypeLabels()
},
{ deep: true }
)
@@ -379,7 +504,6 @@ const addPiece = () => {
ensureArray('pieces')
props.node.pieces.push({
name: '',
reference: '',
quantity: undefined,
typePieceId: '',
typePieceLabel: '',
@@ -397,6 +521,8 @@ const addSubComponent = () => {
name: '',
description: '',
quantity: undefined,
typeComposantId: '',
typeComposantLabel: '',
customFields: [],
pieces: [],
subComponents: [],