Extract useMachineCreatePage composable and 5 preview/selector components from machines/new.vue, reducing it from 1231 to 196 LOC. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
459 lines
16 KiB
TypeScript
459 lines
16 KiB
TypeScript
/**
|
||
* Machine creation page – orchestration composable.
|
||
*
|
||
* Consolidates entity lookup maps, option filters, label helpers,
|
||
* template wrappers, and the finalization logic that were previously
|
||
* inlined in pages/machines/new.vue.
|
||
*/
|
||
|
||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||
import { useMachines } from '~/composables/useMachines'
|
||
import { useSites } from '~/composables/useSites'
|
||
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 { useMachineCreateSelections } from '~/composables/useMachineCreateSelections'
|
||
import {
|
||
useMachineCreatePreview,
|
||
validateRequirementSelections as _validateRequirementSelections,
|
||
resolveComponentRequirementTypeLabel as _resolveComponentRequirementTypeLabel,
|
||
resolvePieceRequirementTypeLabel as _resolvePieceRequirementTypeLabel,
|
||
} from '~/composables/useMachineCreatePreview'
|
||
import {
|
||
getComponentMachineAssignments,
|
||
getPieceMachineAssignments,
|
||
getPieceComponentAssignments,
|
||
formatAssignmentList,
|
||
} from '~/shared/utils/assignmentUtils'
|
||
|
||
export function useMachineCreatePage() {
|
||
// ---------------------------------------------------------------------------
|
||
// Composable calls
|
||
// ---------------------------------------------------------------------------
|
||
|
||
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()
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Local state
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const submitting = ref(false)
|
||
|
||
const newMachine = reactive({
|
||
name: '',
|
||
siteId: '',
|
||
typeMachineId: '',
|
||
reference: '',
|
||
})
|
||
|
||
const selectedMachineType = computed(() => {
|
||
if (!newMachine.typeMachineId) return null
|
||
return (machineTypes as any).value.find((type: any) => type.id === newMachine.typeMachineId) || null
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Entity lookup maps
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const componentById = computed(() => {
|
||
const map = new Map()
|
||
;((composants as any).value || []).forEach((component: any) => {
|
||
if (component?.id) map.set(component.id, component)
|
||
})
|
||
return map
|
||
})
|
||
|
||
const pieceById = computed(() => {
|
||
const map = new Map()
|
||
;((pieces as any).value || []).forEach((piece: any) => {
|
||
if (piece?.id) map.set(piece.id, piece)
|
||
})
|
||
return map
|
||
})
|
||
|
||
const componentInventory = computed(() => (composants as any).value || [])
|
||
const pieceInventory = computed(() => (pieces as any).value || [])
|
||
const productInventory = computed(() => (products as any).value || [])
|
||
|
||
const productById = computed(() => {
|
||
const map = new Map()
|
||
;(productInventory.value || []).forEach((product: any) => {
|
||
if (product?.id) map.set(product.id, product)
|
||
})
|
||
return map
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Entity finders
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const findComponentById = (id: string) => {
|
||
if (!id) return null
|
||
return componentById.value.get(id) || null
|
||
}
|
||
|
||
const findPieceById = (id: string): any => {
|
||
if (!id) return null
|
||
return pieceById.value.get(id) || findPieceInCachedOptions(id) || null
|
||
}
|
||
|
||
const findProductById = (id: string) => {
|
||
if (!id) return null
|
||
return productById.value.get(id) || null
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Selection state (from composable)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const {
|
||
pieceOptionsByKey,
|
||
pieceLoadingByKey,
|
||
selectedPieceIds,
|
||
getPieceKey,
|
||
findPieceInCachedOptions,
|
||
fetchPieceOptions,
|
||
getComponentRequirementEntries,
|
||
getPieceRequirementEntries,
|
||
getProductRequirementEntries,
|
||
addComponentSelectionEntry,
|
||
removeComponentSelectionEntry,
|
||
addPieceSelectionEntry,
|
||
removePieceSelectionEntry,
|
||
addProductSelectionEntry,
|
||
removeProductSelectionEntry,
|
||
setComponentRequirementComponent,
|
||
setPieceRequirementPiece,
|
||
setProductRequirementProduct: _setProductRequirementProduct,
|
||
clearRequirementSelections,
|
||
initializeRequirementSelections,
|
||
} = useMachineCreateSelections({
|
||
findComponentById,
|
||
findPieceById,
|
||
pieces: pieces as any,
|
||
get: get as any,
|
||
toast,
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Preview / validation (from composable)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const { machinePreview, blockingPreviewIssues, canCreateMachine } = useMachineCreatePreview({
|
||
newMachine,
|
||
sites: sites as any,
|
||
selectedMachineType,
|
||
findComponentById,
|
||
findPieceById,
|
||
findProductById,
|
||
getComponentRequirementEntries,
|
||
getPieceRequirementEntries,
|
||
getProductRequirementEntries,
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Template wrappers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const resolveComponentRequirementTypeLabel = (requirement: any, entry: any) =>
|
||
_resolveComponentRequirementTypeLabel(requirement, entry, findComponentById)
|
||
|
||
const resolvePieceRequirementTypeLabel = (requirement: any, entry: any) =>
|
||
_resolvePieceRequirementTypeLabel(requirement, entry, findPieceById)
|
||
|
||
const setProductRequirementProduct = (requirement: any, index: number, productId: string) =>
|
||
_setProductRequirementProduct(requirement, index, productId, findProductById)
|
||
|
||
const validateRequirementSelections = (type: any) =>
|
||
_validateRequirementSelections(type, {
|
||
newMachine,
|
||
sites: sites as any,
|
||
selectedMachineType,
|
||
findComponentById,
|
||
findPieceById,
|
||
findProductById,
|
||
getComponentRequirementEntries,
|
||
getPieceRequirementEntries,
|
||
getProductRequirementEntries,
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Machine type helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const machineTypeLabel = (type: any) => {
|
||
if (!type) return ''
|
||
return type.name || 'Type de machine'
|
||
}
|
||
|
||
const machineTypeDescription = (type: any) => {
|
||
if (!type) return ''
|
||
const parts: string[] = []
|
||
if (type.category) parts.push(`Catégorie : ${type.category}`)
|
||
const componentCount = type.componentRequirements?.length ?? 0
|
||
const pieceCount = type.pieceRequirements?.length ?? 0
|
||
const productCount = type.productRequirements?.length ?? 0
|
||
parts.push(
|
||
`${componentCount} composant(s)`,
|
||
`${pieceCount} pièce(s)`,
|
||
`${productCount} produit(s)`,
|
||
)
|
||
return parts.join(' • ')
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Option filters
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const getComponentOptions = (requirement: any, currentEntry: any) => {
|
||
const requirementTypeId = requirement?.typeComposantId || requirement?.typeComposant?.id || null
|
||
return componentInventory.value.filter((component: any) => {
|
||
if (!component?.id) return false
|
||
if (requirementTypeId && component.typeComposantId !== requirementTypeId) {
|
||
return currentEntry?.composantId === component.id
|
||
}
|
||
return true
|
||
})
|
||
}
|
||
|
||
const getPieceOptions = (requirement: any, currentEntry: any, entryIndex: number) => {
|
||
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: any) => id && (!currentEntry || id !== currentEntry.pieceId)),
|
||
)
|
||
return pieceInventory.value.filter((piece: any) => {
|
||
if (requirementTypeId && piece.typePieceId !== requirementTypeId) return false
|
||
if (!piece.id) return false
|
||
if (currentEntry?.pieceId === piece.id) return true
|
||
return !usedIds.has(piece.id)
|
||
})
|
||
}
|
||
|
||
const getProductOptions = (requirement: any) => {
|
||
const requirementTypeId = requirement?.typeProductId || requirement?.typeProduct?.id || null
|
||
return productInventory.value.filter((product: any) => {
|
||
if (!product?.id) return false
|
||
if (!requirementTypeId) return true
|
||
const productTypeId = product.typeProductId || product.typeProduct?.id || null
|
||
return productTypeId === requirementTypeId
|
||
})
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Option label / description helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const componentOptionLabel = (component: any) => component?.name || 'Composant'
|
||
|
||
const componentOptionDescription = (component: any) => {
|
||
if (!component) return ''
|
||
const parts: string[] = []
|
||
if (component.reference) parts.push(`Réf. ${component.reference}`)
|
||
const constructeurName = component.constructeur?.name || component.constructeurName
|
||
if (constructeurName) parts.push(constructeurName)
|
||
const machineAssignments = getComponentMachineAssignments(component)
|
||
if (machineAssignments.length) parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`)
|
||
const productTypeName = component.product?.typeProduct?.name
|
||
const productLabel = component.product?.name || component.product?.reference
|
||
if (productTypeName || productLabel) parts.push(`Produit: ${productTypeName || productLabel}`)
|
||
return parts.join(' • ')
|
||
}
|
||
|
||
const pieceOptionLabel = (piece: any) => piece?.name || 'Pièce'
|
||
|
||
const pieceOptionDescription = (piece: any) => {
|
||
if (!piece) return ''
|
||
const parts: string[] = []
|
||
if (piece.reference) parts.push(`Réf. ${piece.reference}`)
|
||
const constructeurName = piece.constructeur?.name || piece.constructeurName
|
||
if (constructeurName) parts.push(constructeurName)
|
||
const machineAssignments = getPieceMachineAssignments(piece)
|
||
if (machineAssignments.length) parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`)
|
||
const componentAssignments = getPieceComponentAssignments(piece)
|
||
if (componentAssignments.length) parts.push(`Composants: ${formatAssignmentList(componentAssignments)}`)
|
||
const productTypeName = piece.product?.typeProduct?.name
|
||
const productLabel = piece.product?.name || piece.product?.reference
|
||
if (productTypeName || productLabel) parts.push(`Produit: ${productTypeName || productLabel}`)
|
||
return parts.join(' • ')
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Machine creation
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const finalizeMachineCreation = async () => {
|
||
if (submitting.value) return
|
||
const type = selectedMachineType.value
|
||
if (!type) {
|
||
toast.showError('Merci de sélectionner un type de machine')
|
||
return
|
||
}
|
||
if (!canCreateMachine.value) {
|
||
toast.showError('Compléter les informations obligatoires avant de créer la machine')
|
||
return
|
||
}
|
||
|
||
submitting.value = true
|
||
try {
|
||
const baseMachineData = {
|
||
name: newMachine.name,
|
||
siteId: newMachine.siteId,
|
||
reference: newMachine.reference,
|
||
typeMachineId: type.id,
|
||
}
|
||
|
||
const hasRequirements =
|
||
(type.componentRequirements?.length || 0) > 0 ||
|
||
(type.pieceRequirements?.length || 0) > 0 ||
|
||
(type.productRequirements?.length || 0) > 0
|
||
|
||
let componentLinks: any[] = []
|
||
let pieceLinks: any[] = []
|
||
let productLinks: any[] = []
|
||
|
||
if (hasRequirements) {
|
||
const validationResult = validateRequirementSelections(type)
|
||
if (!validationResult.valid) {
|
||
toast.showError(validationResult.error as string)
|
||
return
|
||
}
|
||
componentLinks = validationResult.componentLinks as any[]
|
||
pieceLinks = validationResult.pieceLinks as any[]
|
||
productLinks = validationResult.productLinks as any[]
|
||
}
|
||
|
||
const result: any = hasRequirements
|
||
? await createMachine(baseMachineData as any)
|
||
: await createMachineFromType(baseMachineData as any, type)
|
||
|
||
if (result.success) {
|
||
if (hasRequirements && result.data?.id) {
|
||
const skeletonResult: any = await reconfigureSkeleton(result.data.id, {
|
||
componentLinks,
|
||
pieceLinks,
|
||
productLinks,
|
||
} as any)
|
||
if (!skeletonResult.success) {
|
||
toast.showError(skeletonResult.error || 'Impossible d\'enregistrer les pièces/composants')
|
||
return
|
||
}
|
||
}
|
||
newMachine.name = ''
|
||
newMachine.siteId = ''
|
||
newMachine.typeMachineId = ''
|
||
newMachine.reference = ''
|
||
clearRequirementSelections()
|
||
await navigateTo('/machines')
|
||
} else if (result.error) {
|
||
toast.showError(`Impossible de créer la machine: ${result.error}`)
|
||
}
|
||
} catch (error: any) {
|
||
toast.showError(`Erreur lors de la création: ${error.message}`)
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Watchers & lifecycle
|
||
// ---------------------------------------------------------------------------
|
||
|
||
watch(
|
||
() => newMachine.typeMachineId,
|
||
(typeId) => {
|
||
clearRequirementSelections()
|
||
if (!typeId) return
|
||
const type = (machineTypes as any).value.find((item: any) => item.id === typeId)
|
||
if (!type) return
|
||
initializeRequirementSelections(type)
|
||
},
|
||
)
|
||
|
||
onMounted(async () => {
|
||
await Promise.all([
|
||
loadSites(),
|
||
loadMachineTypes(),
|
||
loadComposants(),
|
||
loadPieces(),
|
||
loadProducts(),
|
||
])
|
||
})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Public API
|
||
// ---------------------------------------------------------------------------
|
||
|
||
return {
|
||
// State
|
||
submitting,
|
||
newMachine,
|
||
sites,
|
||
machineTypes,
|
||
machineTypesLoading,
|
||
composantsLoading,
|
||
piecesLoading,
|
||
productsLoading,
|
||
selectedMachineType,
|
||
|
||
// Selection state
|
||
pieceLoadingByKey,
|
||
getPieceKey,
|
||
fetchPieceOptions,
|
||
getComponentRequirementEntries,
|
||
getPieceRequirementEntries,
|
||
getProductRequirementEntries,
|
||
addComponentSelectionEntry,
|
||
removeComponentSelectionEntry,
|
||
addPieceSelectionEntry,
|
||
removePieceSelectionEntry,
|
||
addProductSelectionEntry,
|
||
removeProductSelectionEntry,
|
||
setComponentRequirementComponent,
|
||
setPieceRequirementPiece,
|
||
setProductRequirementProduct,
|
||
|
||
// Preview
|
||
machinePreview,
|
||
blockingPreviewIssues,
|
||
canCreateMachine,
|
||
|
||
// Entity finders
|
||
findComponentById,
|
||
findPieceById,
|
||
findProductById,
|
||
|
||
// Options
|
||
getComponentOptions,
|
||
getPieceOptions,
|
||
getProductOptions,
|
||
|
||
// Label helpers
|
||
machineTypeLabel,
|
||
machineTypeDescription,
|
||
componentOptionLabel,
|
||
componentOptionDescription,
|
||
pieceOptionLabel,
|
||
pieceOptionDescription,
|
||
|
||
// Type label resolvers
|
||
resolveComponentRequirementTypeLabel,
|
||
resolvePieceRequirementTypeLabel,
|
||
|
||
// Actions
|
||
finalizeMachineCreation,
|
||
}
|
||
}
|