/** * Machine detail page — core state & business logic (orchestrator). * * Extracted from pages/machine/[id].vue (F1.1). * Composes sub-composables for documents, custom fields, hierarchy and products. */ import { ref, computed, watch } from 'vue' import { useMachines } from '~/composables/useMachines' import { useComposants } from '~/composables/useComposants' import { usePieces } from '~/composables/usePieces' import { useComponentTypes } from '~/composables/useComponentTypes' import { usePieceTypes } from '~/composables/usePieceTypes' import { useCustomFields } from '~/composables/useCustomFields' import { useApi } from '~/composables/useApi' import { useToast } from '~/composables/useToast' import { useConstructeurs } from '~/composables/useConstructeurs' import { useSites } from '~/composables/useSites' import { useMachinePrint } from '~/composables/useMachinePrint' import { resolveConstructeurs, uniqueConstructeurIds, formatConstructeurContact as formatConstructeurContactSummary, parseConstructeurLinksFromApi, constructeurIdsFromLinks, } from '~/shared/constructeurUtils' import type { ConstructeurLinkEntry } from '~/shared/constructeurUtils' import { useConstructeurLinks } from '~/composables/useConstructeurLinks' import { useMachineDetailDocuments } from '~/composables/useMachineDetailDocuments' import { useMachineDetailCustomFields } from '~/composables/useMachineDetailCustomFields' import { useMachineDetailHierarchy } from '~/composables/useMachineDetailHierarchy' import { useMachineDetailProducts } from '~/composables/useMachineDetailProducts' import { useMachineDetailUpdates } from '~/composables/useMachineDetailUpdates' import { downloadDocument as downloadDocumentHelper } from '~/shared/utils/documentDisplayUtils' type AnyRecord = Record export function useMachineDetailData(machineId: string) { // External composables const { updateMachine: updateMachineApi, updateStructure: updateMachineStructure, } = useMachines() const { updateComposant: updateComposantApi } = useComposants() const { updatePiece: updatePieceApi } = usePieces() const { componentTypes, loadComponentTypes } = useComponentTypes() const { pieceTypes, loadPieceTypes } = usePieceTypes() const { upsertCustomFieldValue } = useCustomFields() const { get, patch: apiPatch } = useApi() const toast = useToast() const { constructeurs, loadConstructeurs } = useConstructeurs() const { sites, loadSites } = useSites() const { printModalOpen, printSelection, ensurePrintSelectionEntries: _ensurePrintEntries, setAllPrintSelection: _setAllPrint, openPrintModal: _openPrintModal, closePrintModal, handlePrintConfirm: _handlePrintConfirm, } = useMachinePrint() // Core state const loading = ref(true) const machine = ref(null) const productDocumentsMap = ref>(new Map()) const printAreaRef = ref(null) const saving = ref(false) // Constructeur links const { fetchLinks, syncLinks } = useConstructeurLinks() const constructeurLinks = ref([]) const originalConstructeurLinks = ref([]) // Machine fields const machineName = ref('') const machineReference = ref('') const machineSiteId = ref('') const machineConstructeurIds = ref([]) const machineConstructeurId = computed({ get: () => machineConstructeurIds.value[0] || null, set: (value: string | null) => { machineConstructeurIds.value = value ? [value] : [] }, }) const machineConstructeursDisplay = computed(() => { const ids = machineConstructeurIds.value if (!ids.length) return [] as any[] // Extract nested constructeur objects from link entries as candidate pool const linkConstructeurs = constructeurLinks.value .filter(l => l.constructeur && l.constructeur.id) .map(l => l.constructeur!) as any[] return resolveConstructeurs( ids, linkConstructeurs, constructeurs.value as any, ) as any[] }) const machineConstructeurContact = computed(() => machineConstructeursDisplay.value .map((c: any) => formatConstructeurContactSummary(c)) .filter(Boolean) .join(' • '), ) const hasMachineConstructeur = computed( () => machineConstructeursDisplay.value.length > 0, ) // UI state const isEditMode = ref(false) const canSubmit = computed(() => { if (!machine.value) return false if (saving.value) return false if (!machineName.value.trim()) return false return true }) const debug = ref(false) const componentsCollapsed = ref(true) const collapseToggleToken = ref(0) const piecesCollapsed = ref(true) const pieceCollapseToggleToken = ref(0) // Sub-composables: Products (init first — hierarchy needs findProductById) // Products needs machineProductLinks from hierarchy, but hierarchy needs // findProductById from products. We break the cycle by passing a lazy // computed that reads from the hierarchy ref once it exists. const _machineProductLinksProxy = ref([]) const { productInventory, productById, machineDirectProducts, findProductById, resolveProductReference, getProductDisplay, loadProducts, } = useMachineDetailProducts({ machineProductLinks: _machineProductLinksProxy, productDocumentsMap, constructeurs, }) // Sub-composables: Custom fields const { machineCustomFields, visibleMachineCustomFields, transformCustomFields, transformComponentCustomFields, syncMachineCustomFields, setMachineCustomFieldValue, updateMachineCustomField, updatePieceCustomField, saveAllMachineCustomFields, } = useMachineDetailCustomFields({ machine, isEditMode, constructeurs, resolveProductReference, getProductDisplay, }) // Sub-composables: Hierarchy (includes structure link CRUD) const hierarchy = useMachineDetailHierarchy({ machineId, machine, constructeurs, findProductById, transformComponentCustomFields, transformCustomFields, syncMachineCustomFields, }) const { components, pieces, machineComponentLinks, machinePieceLinks, machineProductLinks, flattenedComponents, machinePieces, applyMachineLinks, reloadMachineStructure, addComponentLink, removeComponentLink, addPieceLink, removePieceLink, addProductLink, removeProductLink, } = hierarchy // Keep the product links proxy in sync with the hierarchy's machineProductLinks watch(machineProductLinks, (val) => { _machineProductLinksProxy.value = val }, { immediate: true }) // Sub-composables: Documents const { machineDocumentFiles, machineDocumentsUploading, machineDocumentsLoaded, previewDocument, previewVisible, machineDocumentsList, refreshMachineDocuments, handleMachineFilesAdded, removeMachineDocument, openPreview, closePreview, loadProductDocuments: _loadProductDocuments, } = useMachineDetailDocuments({ machine }) // Type helpers const componentTypeOptions = computed(() => componentTypes.value || []) const pieceTypeOptions = computed(() => pieceTypes.value || []) const componentTypeLabelMap = computed(() => { const map = new Map() componentTypeOptions.value.forEach((type) => { if (type?.id) map.set(type.id as string, (type.name as string) || '') }) return map }) const pieceTypeLabelMap = computed(() => { const map = new Map() pieceTypeOptions.value.forEach((type) => { if (type?.id) map.set(type.id as string, (type.name as string) || '') }) return map }) // Machine field methods const initMachineFields = () => { if (machine.value) { machineName.value = (machine.value.name as string) || '' machineReference.value = (machine.value.reference as string) || '' // Parse constructeur links from structure response const rawLinks = Array.isArray(machine.value.constructeurs) ? machine.value.constructeurs as any[] : [] const parsed = parseConstructeurLinksFromApi(rawLinks) constructeurLinks.value = parsed originalConstructeurLinks.value = parsed.map(l => ({ ...l })) machineConstructeurIds.value = constructeurIdsFromLinks(parsed) machineSiteId.value = (machine.value.siteId as string) || (machine.value.site as AnyRecord)?.id as string || '' } } const getMachineFieldId = (fieldName: string): string => { return machine.value ? `machine-${fieldName}-${machine.value.id}` : `machine-${fieldName}` } // Product documents wrapper const loadProductDocuments = async () => { const map = await _loadProductDocuments(machineProductLinks.value) productDocumentsMap.value = map } // Update methods (delegated to useMachineDetailUpdates) const { updateMachineInfo, updateComponent, updatePieceFromComponent, updatePieceInfo, handleMachineConstructeurChange, editComponent, editPiece, } = useMachineDetailUpdates({ machine, machineName, machineReference, machineSiteId, machineConstructeurIds, constructeurLinks, originalConstructeurLinks, machineDocumentsLoaded, machineComponentLinks, machinePieceLinks, machineProductLinks, applyMachineLinks, refreshMachineDocuments, transformComponentCustomFields, transformCustomFields, loadProductDocuments, upsertCustomFieldValue, updateMachineApi, updateComposantApi: updateComposantApi, updatePieceApi, apiPatch, toast, syncLinks, }) // UI methods const toggleEditMode = () => { isEditMode.value = !isEditMode.value debug.value = !debug.value if (isEditMode.value && !machineDocumentsLoaded.value) { refreshMachineDocuments() } } const toggleAllComponents = () => { componentsCollapsed.value = !componentsCollapsed.value collapseToggleToken.value += 1 } const collapseAllComponents = () => { componentsCollapsed.value = true collapseToggleToken.value += 1 } const toggleAllPieces = () => { piecesCollapsed.value = !piecesCollapsed.value pieceCollapseToggleToken.value += 1 } const submitEdition = async () => { if (!machine.value || saving.value) return saving.value = true try { // 1. Save machine info (name, reference, site, constructeurs) await updateMachineInfo() // 2. Save all custom field values await saveAllMachineCustomFields() // 3. Reload machine data to get fresh state await loadMachineData() // 4. Exit edit mode isEditMode.value = false toast.showSuccess('Machine mise à jour avec succès') } catch (error) { console.error('Erreur lors de la sauvegarde:', error) toast.showError('Erreur lors de la sauvegarde de la machine') } finally { saving.value = false } } const cancelEdition = () => { initMachineFields() syncMachineCustomFields() constructeurLinks.value = originalConstructeurLinks.value.map(l => ({ ...l })) machineConstructeurIds.value = constructeurIdsFromLinks(constructeurLinks.value) isEditMode.value = false } // Print wrappers const ensurePrintSelectionEntries = () => _ensurePrintEntries(components.value, machinePieces.value) const setAllPrintSelection = (value: boolean) => _setAllPrint(value, components.value, machinePieces.value) const openPrintModal = () => _openPrintModal(components.value, machinePieces.value) const handlePrintConfirm = () => _handlePrintConfirm( machine.value as any, machineName.value, machineReference.value, machinePieces.value as any, components.value as any, ) // Data loading const loadMachineData = async () => { loading.value = true try { const machineResult: any = await get(`/machines/${machineId}/structure`) if (!machineResult.success) { console.error('Machine non trouvée:', machineId, machineResult.error) machine.value = null components.value = [] pieces.value = [] return } const machinePayload = machineResult.data?.machine && typeof machineResult.data.machine === 'object' ? machineResult.data.machine : machineResult.data if (!machinePayload || typeof machinePayload !== 'object') { console.error('Réponse machine invalide pour', machineId) machine.value = null components.value = [] pieces.value = [] return } machine.value = { ...machinePayload, documents: machinePayload.documents || [], customFieldValues: machinePayload.customFieldValues || [], } machineDocumentsLoaded.value = !!((machine.value!.documents as AnyRecord[])?.length) syncMachineCustomFields() initMachineFields() // Start document loading early (independent of products/links) const documentPromise = !machineDocumentsLoaded.value ? refreshMachineDocuments() : Promise.resolve() // Load products in parallel — don't block hierarchy rendering const productsPromise = !(productInventory.value as AnyRecord[]).length ? loadProducts().catch((error: unknown) => { console.error('Erreur lors du chargement des produits:', error) }) : Promise.resolve() await productsPromise const linksApplied = applyMachineLinks(machineResult.data) if (machine.value) { machine.value.componentLinks = machineComponentLinks.value machine.value.pieceLinks = machinePieceLinks.value machine.value.productLinks = machineProductLinks.value } if (!linksApplied) { components.value = transformComponentCustomFields(machinePayload.components || []) pieces.value = transformCustomFields(machinePayload.pieces || []) machineProductLinks.value = Array.isArray(machinePayload.productLinks) ? machinePayload.productLinks : [] } if (machine.value) { machine.value.productLinks = machineProductLinks.value } collapseAllComponents() // Load product documents in background loadProductDocuments().catch(() => {}) // Wait for documents if still loading await documentPromise } catch (error) { console.error('Erreur lors du chargement des données:', error) } finally { loading.value = false } } const loadInitialData = (): Promise => { return Promise.all([ loadConstructeurs(), loadComponentTypes(), loadPieceTypes(), loadSites(), ]) } // Watchers watch(() => (machine.value as AnyRecord)?.customFieldValues, () => syncMachineCustomFields(), { deep: true }) watch(() => (machine.value as AnyRecord)?.customFields, () => syncMachineCustomFields(), { deep: true }) watch( () => [components.value.length, machinePieces.value.length], () => ensurePrintSelectionEntries(), { immediate: true }, ) return { // State loading, machine, components, pieces, printAreaRef, machineComponentLinks, machinePieceLinks, machineProductLinks, // Machine fields machineName, machineReference, machineSiteId, machineConstructeurIds, machineConstructeurId, machineConstructeursDisplay, machineConstructeurContact, hasMachineConstructeur, constructeurLinks, originalConstructeurLinks, sites, // UI state machineDocumentFiles, machineDocumentsUploading, machineDocumentsLoaded, machineCustomFields, previewDocument, previewVisible, isEditMode, debug, componentsCollapsed, collapseToggleToken, piecesCollapsed, pieceCollapseToggleToken, // Computed componentTypeOptions, pieceTypeOptions, componentTypeLabelMap, pieceTypeLabelMap, productInventory, productById, flattenedComponents, machinePieces, machineDirectProducts, machineDocumentsList, visibleMachineCustomFields, // Methods findProductById, resolveProductReference, getProductDisplay, initMachineFields, getMachineFieldId, syncMachineCustomFields, setMachineCustomFieldValue, updateMachineCustomField, updatePieceCustomField, refreshMachineDocuments, handleMachineFilesAdded, removeMachineDocument, openPreview, closePreview, updateMachineInfo, updateComponent, updatePieceFromComponent, updatePieceInfo, handleMachineConstructeurChange, editComponent, editPiece, toggleEditMode, toggleAllComponents, collapseAllComponents, toggleAllPieces, saving, canSubmit, submitEdition, cancelEdition, // Print printModalOpen, printSelection, ensurePrintSelectionEntries, setAllPrintSelection, openPrintModal, closePrintModal, handlePrintConfirm, // Loading & structure loadMachineData, loadInitialData, addComponentLink, removeComponentLink, addPieceLink, removePieceLink, addProductLink, removeProductLink, reloadMachineStructure, // External constructeurs, loadProducts, updateMachineStructure, toast, downloadDocument: downloadDocumentHelper, } }