/** * Machine creation – requirement selection state management. * * Extracted from pages/machines/new.vue. Manages the reactive selection state * for component / piece / product requirements when creating a new machine. */ import { ref, reactive, computed } from 'vue' type AnyRecord = Record export interface MachineCreateSelectionsDeps { findComponentById: (id: string) => AnyRecord | null findPieceById: (id: string) => AnyRecord | null pieces: { value: AnyRecord[] } get: (url: string) => Promise toast: { showError: (msg: string) => void } } const extractCollection = (payload: unknown): unknown[] => { if (Array.isArray(payload)) return payload if (Array.isArray((payload as AnyRecord)?.member)) return (payload as AnyRecord).member as unknown[] if (Array.isArray((payload as AnyRecord)?.['hydra:member'])) return (payload as AnyRecord)['hydra:member'] as unknown[] if (Array.isArray((payload as AnyRecord)?.data)) return (payload as AnyRecord).data as unknown[] return [] } export function useMachineCreateSelections(deps: MachineCreateSelectionsDeps) { const { findComponentById, findPieceById, pieces, get, toast } = deps // --------------------------------------------------------------------------- // Reactive state // --------------------------------------------------------------------------- const componentRequirementSelections = reactive>({}) const pieceRequirementSelections = reactive>({}) const productRequirementSelections = reactive>({}) const pieceOptionsByKey = ref>({}) const pieceLoadingByKey = ref>({}) // --------------------------------------------------------------------------- // Piece option caching // --------------------------------------------------------------------------- const getPieceKey = (requirement: AnyRecord, entryIndex: number): string => `${requirement?.id || 'req'}:${entryIndex}` const findPieceInCachedOptions = (id: string): AnyRecord | null => { 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: AnyRecord): void => { if (!piece?.id) return const current = Array.isArray(pieces.value) ? pieces.value : [] if (current.some((p: AnyRecord) => p?.id === piece.id)) return pieces.value = [...current, piece] } const fetchPieceOptions = async ( requirement: AnyRecord, entryIndex: number, term = '', ): Promise => { const key = getPieceKey(requirement, entryIndex) if (pieceLoadingByKey.value[key]) return const requirementTypeId = (requirement?.typePieceId || (requirement?.typePiece as AnyRecord)?.id || null) as string | 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) as AnyRecord[], } } } finally { pieceLoadingByKey.value = { ...pieceLoadingByKey.value, [key]: false } } } // --------------------------------------------------------------------------- // Entry getters // --------------------------------------------------------------------------- const getComponentRequirementEntries = (requirementId: string): AnyRecord[] => componentRequirementSelections[requirementId] || [] const getPieceRequirementEntries = (requirementId: string): AnyRecord[] => pieceRequirementSelections[requirementId] || [] const getProductRequirementEntries = (requirementId: string): AnyRecord[] => productRequirementSelections[requirementId] || [] // --------------------------------------------------------------------------- // Entry factories // --------------------------------------------------------------------------- const createComponentSelectionEntry = (requirement: AnyRecord, source: AnyRecord | null = null): AnyRecord => ({ typeComposantId: requirement?.typeComposantId || (requirement?.typeComposant as AnyRecord)?.id || null, composantId: source?.composantId || null, definition: {}, }) const createPieceSelectionEntry = (requirement: AnyRecord, source: AnyRecord | null = null): AnyRecord => ({ typePieceId: requirement?.typePieceId || (requirement?.typePiece as AnyRecord)?.id || null, pieceId: source?.pieceId || null, definition: {}, }) const createProductSelectionEntry = (requirement: AnyRecord, source: AnyRecord | null = null): AnyRecord => ({ typeProductId: source?.typeProductId || requirement?.typeProductId || (requirement?.typeProduct as AnyRecord)?.id || null, productId: source?.productId || null, }) // --------------------------------------------------------------------------- // Selected piece IDs (for dedup) // --------------------------------------------------------------------------- const selectedPieceIds = computed(() => { const ids: string[] = [] Object.values(pieceRequirementSelections).forEach((entries) => { ;(entries || []).forEach((entry) => { if (entry?.pieceId) ids.push(entry.pieceId as string) }) }) return ids }) // --------------------------------------------------------------------------- // CRUD operations // --------------------------------------------------------------------------- const addComponentSelectionEntry = (requirement: AnyRecord): void => { const entries = getComponentRequirementEntries(requirement.id as string) const max = (requirement.maxCount ?? null) as number | null if (max !== null && entries.length >= max) { toast.showError( `Vous ne pouvez pas ajouter plus de ${max} composant(s) pour ${requirement.label || (requirement.typeComposant as AnyRecord)?.name || 'ce groupe'}`, ) return } componentRequirementSelections[requirement.id as string] = [ ...entries, createComponentSelectionEntry(requirement), ] } const removeComponentSelectionEntry = (requirementId: string, index: number): void => { const entries = getComponentRequirementEntries(requirementId) componentRequirementSelections[requirementId] = entries.filter((_: unknown, i: number) => i !== index) } const addPieceSelectionEntry = (requirement: AnyRecord): void => { const entries = getPieceRequirementEntries(requirement.id as string) const max = (requirement.maxCount ?? null) as number | null if (max !== null && entries.length >= max) { toast.showError( `Vous ne pouvez pas ajouter plus de ${max} pièce(s) pour ${requirement.label || (requirement.typePiece as AnyRecord)?.name || 'ce groupe'}`, ) return } pieceRequirementSelections[requirement.id as string] = [ ...entries, createPieceSelectionEntry(requirement), ] fetchPieceOptions(requirement, entries.length).catch(() => {}) } const removePieceSelectionEntry = (requirementId: string, index: number): void => { const entries = getPieceRequirementEntries(requirementId) pieceRequirementSelections[requirementId] = entries.filter((_: unknown, i: number) => i !== index) } const addProductSelectionEntry = (requirement: AnyRecord): void => { const entries = getProductRequirementEntries(requirement.id as string) const max = (requirement.maxCount ?? null) as number | null if (max !== null && entries.length >= max) { toast.showError( `Vous ne pouvez pas ajouter plus de ${max} produit(s) pour ${requirement.label || (requirement.typeProduct as AnyRecord)?.name || 'ce groupe'}`, ) return } productRequirementSelections[requirement.id as string] = [ ...entries, createProductSelectionEntry(requirement), ] } const removeProductSelectionEntry = (requirementId: string, index: number): void => { const entries = getProductRequirementEntries(requirementId) productRequirementSelections[requirementId] = entries.filter((_: unknown, i: number) => i !== index) } // --------------------------------------------------------------------------- // Selection setters // --------------------------------------------------------------------------- const setComponentRequirementComponent = ( requirement: AnyRecord, index: number, componentId: string, ): void => { const entries = getComponentRequirementEntries(requirement.id as string) const entry = entries[index] if (!entry) return entry.composantId = componentId || null if (componentId) { const component = findComponentById(componentId) entry.typeComposantId = component?.typeComposantId || requirement?.typeComposantId || null } else { entry.typeComposantId = requirement?.typeComposantId || null } } const setPieceRequirementPiece = ( requirement: AnyRecord, index: number, pieceId: string, ): void => { const entries = getPieceRequirementEntries(requirement.id as string) const entry = entries[index] if (!entry) return entry.pieceId = pieceId || null if (pieceId) { const piece = findPieceById(pieceId) || findPieceInCachedOptions(pieceId) entry.typePieceId = piece?.typePieceId || requirement?.typePieceId || null if (piece) cachePieceIfMissing(piece as AnyRecord) } else { entry.typePieceId = requirement?.typePieceId || null } } const setProductRequirementProduct = ( requirement: AnyRecord, index: number, productId: string, findProductById: (id: string) => AnyRecord | null, ): void => { const entries = getProductRequirementEntries(requirement.id as string) const entry = entries[index] if (!entry) return const normalizedProductId = productId || null entry.productId = normalizedProductId if (normalizedProductId) { const product = findProductById(normalizedProductId) entry.typeProductId = product?.typeProductId || (product?.typeProduct as AnyRecord)?.id || entry.typeProductId || requirement?.typeProductId || (requirement?.typeProduct as AnyRecord)?.id || null } else { entry.typeProductId = requirement?.typeProductId || (requirement?.typeProduct as AnyRecord)?.id || null } } // --------------------------------------------------------------------------- // Bulk operations // --------------------------------------------------------------------------- const clearRequirementSelections = (): void => { Object.keys(componentRequirementSelections).forEach((key) => { delete componentRequirementSelections[key] }) Object.keys(pieceRequirementSelections).forEach((key) => { delete pieceRequirementSelections[key] }) Object.keys(productRequirementSelections).forEach((key) => { delete productRequirementSelections[key] }) } const initializeRequirementSelections = (type: AnyRecord): void => { const componentRequirements = (type.componentRequirements || []) as AnyRecord[] const pieceRequirements = (type.pieceRequirements || []) as AnyRecord[] const productRequirements = (type.productRequirements || []) as AnyRecord[] componentRequirements.forEach((requirement) => { const min = (requirement.minCount ?? (requirement.required ? 1 : 0)) as number const initialCount = Math.max(min, requirement.required ? 1 : 0) if (initialCount > 0) { componentRequirementSelections[requirement.id as string] = Array.from( { length: initialCount }, () => createComponentSelectionEntry(requirement), ) } else { componentRequirementSelections[requirement.id as string] = [] } }) pieceRequirements.forEach((requirement) => { const min = (requirement.minCount ?? (requirement.required ? 1 : 0)) as number const initialCount = Math.max(min, requirement.required ? 1 : 0) if (initialCount > 0) { pieceRequirementSelections[requirement.id as string] = Array.from( { length: initialCount }, () => createPieceSelectionEntry(requirement), ) pieceRequirementSelections[requirement.id as string].forEach((_: unknown, index: number) => { fetchPieceOptions(requirement, index).catch(() => {}) }) } else { pieceRequirementSelections[requirement.id as string] = [] } }) productRequirements.forEach((requirement) => { const min = (requirement.minCount ?? (requirement.required ? 1 : 0)) as number const initialCount = Math.max(min, requirement.required ? 1 : 0) if (initialCount > 0) { productRequirementSelections[requirement.id as string] = Array.from( { length: initialCount }, () => createProductSelectionEntry(requirement), ) } else { productRequirementSelections[requirement.id as string] = [] } }) } return { componentRequirementSelections, pieceRequirementSelections, productRequirementSelections, pieceOptionsByKey, pieceLoadingByKey, selectedPieceIds, getPieceKey, findPieceInCachedOptions, fetchPieceOptions, getComponentRequirementEntries, getPieceRequirementEntries, getProductRequirementEntries, addComponentSelectionEntry, removeComponentSelectionEntry, addPieceSelectionEntry, removePieceSelectionEntry, addProductSelectionEntry, removeProductSelectionEntry, setComponentRequirementComponent, setPieceRequirementPiece, setProductRequirementProduct, clearRequirementSelections, initializeRequirementSelections, } }