wip: machine create skeleton links

This commit is contained in:
Matthieu
2026-01-24 00:58:06 +01:00
parent a8cb4d1ac0
commit 1f5f1509a9
3 changed files with 209 additions and 23 deletions

View File

@@ -273,18 +273,19 @@
</label>
<SearchSelect
:model-value="entry.pieceId || ''"
:options="getPieceOptions(requirement, entry)"
:loading="piecesLoading"
:options="getPieceOptions(requirement, entry, entryIndex)"
:loading="piecesLoading || pieceLoadingByKey[getPieceKey(requirement, entryIndex)]"
size="sm"
placeholder="Rechercher une pièce…"
empty-text="Aucune pièce disponible"
:option-label="pieceOptionLabel"
:option-description="pieceOptionDescription"
@search="(term) => fetchPieceOptions(requirement, entryIndex, term)"
@update:modelValue="setPieceRequirementPiece(requirement, entryIndex, $event || '')"
/>
</div>
<p
v-if="getPieceOptions(requirement, entry).length === 0"
v-if="getPieceOptions(requirement, entry, entryIndex).length === 0"
class="text-xs text-error"
>
Aucune pièce disponible pour cette famille.
@@ -743,6 +744,7 @@ import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useComposants } from '~/composables/useComposants'
import { usePieces } from '~/composables/usePieces'
import { useProducts } from '~/composables/useProducts'
import { useApi } from '~/composables/useApi'
import { useToast } from '~/composables/useToast'
import { sanitizeDefinitionOverrides } from '~/shared/modelUtils'
import SearchSelect from '~/components/common/SearchSelect.vue'
@@ -754,12 +756,13 @@ import IconLucideAlertTriangle from '~icons/lucide/alert-triangle'
import IconLucideCheckCircle2 from '~icons/lucide/check-circle-2'
import IconLucideCircle from '~icons/lucide/circle'
const { createMachine, createMachineFromType } = useMachines()
const { createMachine, createMachineFromType, reconfigureSkeleton } = useMachines()
const { sites, loadSites } = useSites()
const { machineTypes, loadMachineTypes, loading: machineTypesLoading } = useMachineTypesApi()
const { composants, loadComposants, loading: composantsLoading } = useComposants()
const { pieces, loadPieces, loading: piecesLoading } = usePieces()
const { products, loadProducts, loading: productsLoading } = useProducts()
const { get } = useApi()
const toast = useToast()
const submitting = ref(false)
@@ -842,6 +845,85 @@ const productById = computed(() => {
return map
})
const pieceOptionsByKey = ref({})
const pieceLoadingByKey = ref({})
const extractCollection = (payload) => {
if (Array.isArray(payload)) {
return payload
}
if (Array.isArray(payload?.member)) {
return payload.member
}
if (Array.isArray(payload?.['hydra:member'])) {
return payload['hydra:member']
}
if (Array.isArray(payload?.data)) {
return payload.data
}
return []
}
const getPieceKey = (requirement, entryIndex) => `${requirement?.id || 'req'}:${entryIndex}`
const findPieceInCachedOptions = (id) => {
if (!id) {
return null
}
const buckets = Object.values(pieceOptionsByKey.value || {})
for (const bucket of buckets) {
if (!Array.isArray(bucket)) {
continue
}
const found = bucket.find((piece) => piece?.id === id)
if (found) {
return found
}
}
return null
}
const cachePieceIfMissing = (piece) => {
if (!piece?.id) {
return
}
if (pieceById.value.has(piece.id)) {
return
}
const current = Array.isArray(pieces.value) ? pieces.value : []
pieces.value = [...current, piece]
}
const fetchPieceOptions = async (requirement, entryIndex, term = '') => {
const key = getPieceKey(requirement, entryIndex)
if (pieceLoadingByKey.value[key]) {
return
}
const requirementTypeId = requirement?.typePieceId || requirement?.typePiece?.id || null
const params = new URLSearchParams()
params.set('itemsPerPage', '50')
if (term && term.trim()) {
params.set('name', term.trim())
}
if (requirementTypeId) {
params.set('typePiece', `/api/model_types/${requirementTypeId}`)
}
pieceLoadingByKey.value = { ...pieceLoadingByKey.value, [key]: true }
try {
const result = await get(`/pieces?${params.toString()}`)
if (result.success) {
pieceOptionsByKey.value = {
...pieceOptionsByKey.value,
[key]: extractCollection(result.data)
}
}
} finally {
pieceLoadingByKey.value = { ...pieceLoadingByKey.value, [key]: false }
}
}
const isPlainObject = value => value !== null && typeof value === 'object' && !Array.isArray(value)
const toTrimmedString = (value) => {
@@ -1077,7 +1159,12 @@ const getComponentOptions = (requirement, currentEntry) => {
})
}
const getPieceOptions = (requirement, currentEntry) => {
const getPieceOptions = (requirement, currentEntry, entryIndex) => {
const key = getPieceKey(requirement, entryIndex)
const cached = pieceOptionsByKey.value[key]
if (cached) {
return cached
}
const requirementTypeId = requirement?.typePieceId || requirement?.typePiece?.id || null
const usedIds = new Set(
selectedPieceIds.value.filter((id) => id && (!currentEntry || id !== currentEntry.pieceId)),
@@ -1241,8 +1328,11 @@ const setPieceRequirementPiece = (requirement, index, pieceId) => {
if (!entry) return
entry.pieceId = pieceId || null
if (pieceId) {
const piece = findPieceById(pieceId)
const piece = findPieceById(pieceId) || findPieceInCachedOptions(pieceId)
entry.typePieceId = piece?.typePieceId || requirement?.typePieceId || null
if (piece) {
cachePieceIfMissing(piece)
}
} else {
entry.typePieceId = requirement?.typePieceId || null
}
@@ -1259,7 +1349,7 @@ const findPieceById = (id) => {
if (!id) {
return null
}
return pieceById.value.get(id) || null
return pieceById.value.get(id) || findPieceInCachedOptions(id) || null
}
const findProductById = (id) => {
@@ -1519,6 +1609,7 @@ const addPieceSelectionEntry = (requirement) => {
...entries,
createPieceSelectionEntry(requirement),
]
fetchPieceOptions(requirement, entries.length).catch(() => {})
}
const removePieceSelectionEntry = (requirementId, index) => {
@@ -2096,6 +2187,9 @@ const initializeRequirementSelections = (type) => {
const initialCount = Math.max(min, requirement.required ? 1 : 0)
if (initialCount > 0) {
pieceRequirementSelections[requirement.id] = Array.from({ length: initialCount }, () => createPieceSelectionEntry(requirement))
pieceRequirementSelections[requirement.id].forEach((_, index) => {
fetchPieceOptions(requirement, index).catch(() => {})
})
} else {
pieceRequirementSelections[requirement.id] = []
}
@@ -2158,22 +2252,22 @@ const finalizeMachineCreation = async () => {
productLinks = validationResult.productLinks
}
const payload = {
...baseMachineData,
...(hasRequirements
? {
componentLinks,
pieceLinks,
productLinks
}
: {})
}
const result = hasRequirements
? await createMachine(payload)
? await createMachine(baseMachineData)
: await createMachineFromType(baseMachineData, type)
if (result.success) {
if (hasRequirements && result.data?.id) {
const skeletonResult = await reconfigureSkeleton(result.data.id, {
componentLinks,
pieceLinks,
productLinks,
})
if (!skeletonResult.success) {
toast.showError(skeletonResult.error || 'Impossible d\'enregistrer les pièces/composants')
return
}
}
newMachine.name = ''
newMachine.siteId = ''
newMachine.typeMachineId = ''