refactor(frontend) : split useMachineDetailData into focused composables
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
396
app/composables/useMachineDetailCustomFields.ts
Normal file
396
app/composables/useMachineDetailCustomFields.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* Machine detail — custom field management sub-composable.
|
||||
*
|
||||
* Handles custom field resolution, display filtering, sync and updates
|
||||
* for machines, components and pieces.
|
||||
*/
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { useCustomFields } from '~/composables/useCustomFields'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { normalizeStructureForEditor } from '~/shared/modelUtils'
|
||||
import {
|
||||
shouldDisplayCustomField,
|
||||
normalizeExistingCustomFieldDefinitions,
|
||||
normalizeCustomFieldValueEntry,
|
||||
mergeCustomFieldValuesWithDefinitions,
|
||||
dedupeCustomFieldEntries,
|
||||
} from '~/shared/utils/customFieldUtils'
|
||||
import {
|
||||
resolveConstructeurs,
|
||||
uniqueConstructeurIds,
|
||||
} from '~/shared/constructeurUtils'
|
||||
|
||||
type AnyRecord = Record<string, unknown>
|
||||
|
||||
interface MachineDetailCustomFieldsDeps {
|
||||
machine: Ref<AnyRecord | null>
|
||||
isEditMode: Ref<boolean>
|
||||
constructeurs: Ref<unknown[]>
|
||||
resolveProductReference: (source: AnyRecord) => { product: unknown; productId: string | null }
|
||||
getProductDisplay: (source: AnyRecord) => unknown
|
||||
}
|
||||
|
||||
export function useMachineDetailCustomFields(deps: MachineDetailCustomFieldsDeps) {
|
||||
const { machine, isEditMode, constructeurs, resolveProductReference, getProductDisplay } = deps
|
||||
const {
|
||||
upsertCustomFieldValue,
|
||||
updateCustomFieldValue: updateCustomFieldValueApi,
|
||||
} = useCustomFields()
|
||||
const toast = useToast()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const machineCustomFields = ref<AnyRecord[]>([])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Computed
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const visibleMachineCustomFields = computed(() => {
|
||||
const fields = Array.isArray(machineCustomFields.value) ? machineCustomFields.value : []
|
||||
if (isEditMode.value) return fields
|
||||
return fields.filter((field) => shouldDisplayCustomField(field))
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transform helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const getStructureCustomFields = (structure: unknown): AnyRecord[] => {
|
||||
if (!structure || typeof structure !== 'object') return []
|
||||
const normalized = normalizeStructureForEditor(structure as any) as any
|
||||
return Array.isArray(normalized?.customFields)
|
||||
? (normalized.customFields as AnyRecord[])
|
||||
: []
|
||||
}
|
||||
|
||||
const transformCustomFields = (piecesData: AnyRecord[]): AnyRecord[] => {
|
||||
return (piecesData || []).map((piece) => {
|
||||
const typePiece = (piece.typePiece as AnyRecord) || {}
|
||||
|
||||
const normalizeStructureDefs = (structure: unknown) =>
|
||||
structure ? normalizeStructureForEditor(structure as AnyRecord) : null
|
||||
|
||||
const normalizedStructureDefs = [
|
||||
normalizeStructureDefs((piece.definition as AnyRecord)?.structure),
|
||||
normalizeStructureDefs(typePiece.structure),
|
||||
]
|
||||
|
||||
const valueEntries = [
|
||||
...(Array.isArray(piece.customFieldValues) ? piece.customFieldValues : []),
|
||||
...(Array.isArray(piece.customFields)
|
||||
? (piece.customFields as AnyRecord[])
|
||||
.map(normalizeCustomFieldValueEntry)
|
||||
.filter((e) => e !== null)
|
||||
: []),
|
||||
...(Array.isArray(typePiece.customFieldValues)
|
||||
? (typePiece.customFieldValues as AnyRecord[])
|
||||
.map(normalizeCustomFieldValueEntry)
|
||||
.filter((e) => e !== null)
|
||||
: []),
|
||||
]
|
||||
|
||||
const customFields = dedupeCustomFieldEntries(
|
||||
mergeCustomFieldValuesWithDefinitions(
|
||||
valueEntries,
|
||||
normalizeExistingCustomFieldDefinitions(piece.customFields),
|
||||
normalizeExistingCustomFieldDefinitions((piece.definition as AnyRecord)?.customFields),
|
||||
normalizeExistingCustomFieldDefinitions((piece.typePiece as AnyRecord)?.customFields),
|
||||
normalizeExistingCustomFieldDefinitions(typePiece.customFields),
|
||||
...normalizedStructureDefs.map((def) => getStructureCustomFields(def)),
|
||||
),
|
||||
)
|
||||
|
||||
const constructeurIds = uniqueConstructeurIds(
|
||||
piece.constructeurs,
|
||||
piece.constructeurIds,
|
||||
piece.constructeurId,
|
||||
piece.constructeur,
|
||||
(piece.originalPiece as AnyRecord)?.constructeurs,
|
||||
(piece.originalPiece as AnyRecord)?.constructeurIds,
|
||||
(piece.originalPiece as AnyRecord)?.constructeurId,
|
||||
(piece.originalPiece as AnyRecord)?.constructeur,
|
||||
)
|
||||
|
||||
const { product: resolvedProduct, productId: resolvedProductId } =
|
||||
resolveProductReference(piece)
|
||||
|
||||
const constructeursList = resolveConstructeurs(
|
||||
constructeurIds,
|
||||
Array.isArray(piece.constructeurs) ? (piece.constructeurs as any[]) : [],
|
||||
piece.constructeur ? [piece.constructeur as any] : [],
|
||||
Array.isArray((piece.originalPiece as AnyRecord)?.constructeurs)
|
||||
? ((piece.originalPiece as AnyRecord).constructeurs as any[])
|
||||
: [],
|
||||
(piece.originalPiece as AnyRecord)?.constructeur
|
||||
? [(piece.originalPiece as AnyRecord).constructeur as any]
|
||||
: [],
|
||||
constructeurs.value as any,
|
||||
) as any[]
|
||||
|
||||
const normalizedPiece = {
|
||||
...piece,
|
||||
product: resolvedProduct || piece.product || null,
|
||||
productId: resolvedProductId || piece.productId || (piece.product as AnyRecord)?.id || null,
|
||||
}
|
||||
const productDisplay = getProductDisplay(normalizedPiece)
|
||||
|
||||
return {
|
||||
...normalizedPiece,
|
||||
customFields,
|
||||
documents: piece.documents || [],
|
||||
constructeurs: constructeursList,
|
||||
constructeur: constructeursList[0] || piece.constructeur || null,
|
||||
constructeurIds,
|
||||
constructeurId: constructeurIds[0] || null,
|
||||
typePieceId:
|
||||
piece.typePieceId ||
|
||||
(piece.typePiece as AnyRecord)?.id ||
|
||||
null,
|
||||
__productDisplay: productDisplay,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const transformComponentCustomFields = (componentsData: AnyRecord[]): AnyRecord[] => {
|
||||
const normalizeStructureDefs = (structure: unknown) =>
|
||||
structure ? normalizeStructureForEditor(structure as AnyRecord) : null
|
||||
|
||||
return (componentsData || []).map((component) => {
|
||||
const type = (component.typeComposant as AnyRecord) || {}
|
||||
|
||||
const normalizedStructureDefs = [
|
||||
normalizeStructureDefs((component.definition as AnyRecord)?.structure),
|
||||
normalizeStructureDefs(type.structure),
|
||||
]
|
||||
|
||||
const actualComponent = (component.originalComposant as AnyRecord) || component
|
||||
|
||||
const valueEntries = [
|
||||
...(Array.isArray(component.customFieldValues) ? component.customFieldValues : []),
|
||||
...(Array.isArray(component.customFields)
|
||||
? (component.customFields as AnyRecord[])
|
||||
.map(normalizeCustomFieldValueEntry)
|
||||
.filter((e) => e !== null)
|
||||
: []),
|
||||
...(Array.isArray(actualComponent?.customFields)
|
||||
? (actualComponent.customFields as AnyRecord[])
|
||||
.map(normalizeCustomFieldValueEntry)
|
||||
.filter((e) => e !== null)
|
||||
: []),
|
||||
]
|
||||
|
||||
const customFields = dedupeCustomFieldEntries(
|
||||
mergeCustomFieldValuesWithDefinitions(
|
||||
valueEntries,
|
||||
normalizeExistingCustomFieldDefinitions(component.customFields),
|
||||
normalizeExistingCustomFieldDefinitions((component.definition as AnyRecord)?.customFields),
|
||||
normalizeExistingCustomFieldDefinitions((component.typeComposant as AnyRecord)?.customFields),
|
||||
normalizeExistingCustomFieldDefinitions(type.customFields),
|
||||
normalizeExistingCustomFieldDefinitions(actualComponent?.customFields),
|
||||
...normalizedStructureDefs.map((def) => getStructureCustomFields(def)),
|
||||
),
|
||||
)
|
||||
|
||||
const piecesTransformed = component.pieces
|
||||
? transformCustomFields(component.pieces as AnyRecord[]).map((p) => ({
|
||||
...p,
|
||||
parentComponentName: component.name,
|
||||
}))
|
||||
: []
|
||||
|
||||
const subComponents = component.sousComposants
|
||||
? transformComponentCustomFields(component.sousComposants as AnyRecord[])
|
||||
: []
|
||||
|
||||
const constructeurIds = uniqueConstructeurIds(
|
||||
component.constructeurs,
|
||||
component.constructeurIds,
|
||||
component.constructeurId,
|
||||
component.constructeur,
|
||||
actualComponent?.constructeurs,
|
||||
actualComponent?.constructeurIds,
|
||||
actualComponent?.constructeurId,
|
||||
actualComponent?.constructeur,
|
||||
)
|
||||
|
||||
const constructeursList = resolveConstructeurs(
|
||||
constructeurIds,
|
||||
Array.isArray(component.constructeurs) ? (component.constructeurs as any[]) : [],
|
||||
component.constructeur ? [component.constructeur as any] : [],
|
||||
Array.isArray(actualComponent?.constructeurs)
|
||||
? (actualComponent.constructeurs as any[])
|
||||
: [],
|
||||
actualComponent?.constructeur ? [actualComponent.constructeur as any] : [],
|
||||
constructeurs.value as any,
|
||||
) as any[]
|
||||
|
||||
const { product: resolvedProduct, productId: resolvedProductId } =
|
||||
resolveProductReference(component)
|
||||
const normalizedComponent = {
|
||||
...component,
|
||||
product: resolvedProduct || component.product || null,
|
||||
productId:
|
||||
resolvedProductId || component.productId || (component.product as AnyRecord)?.id || null,
|
||||
}
|
||||
const productDisplay = getProductDisplay(normalizedComponent)
|
||||
|
||||
return {
|
||||
...normalizedComponent,
|
||||
customFields,
|
||||
pieces: piecesTransformed,
|
||||
subComponents,
|
||||
documents: component.documents || [],
|
||||
constructeurs: constructeursList,
|
||||
constructeur: constructeursList[0] || component.constructeur || null,
|
||||
constructeurIds,
|
||||
constructeurId: constructeurIds[0] || null,
|
||||
typeComposantId:
|
||||
component.typeComposantId ||
|
||||
(component.typeComposant as AnyRecord)?.id ||
|
||||
null,
|
||||
__productDisplay: productDisplay,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Machine custom field methods
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const syncMachineCustomFields = () => {
|
||||
if (!machine.value) {
|
||||
machineCustomFields.value = []
|
||||
return
|
||||
}
|
||||
const valueEntries = [
|
||||
...(Array.isArray(machine.value.customFieldValues) ? machine.value.customFieldValues : []),
|
||||
...(Array.isArray(machine.value.customFields)
|
||||
? (machine.value.customFields as AnyRecord[])
|
||||
.map(normalizeCustomFieldValueEntry)
|
||||
.filter((e) => e !== null)
|
||||
: []),
|
||||
]
|
||||
const merged = dedupeCustomFieldEntries(
|
||||
mergeCustomFieldValuesWithDefinitions(
|
||||
valueEntries,
|
||||
normalizeExistingCustomFieldDefinitions(machine.value.customFields),
|
||||
),
|
||||
).map((field: AnyRecord) => ({ ...field, readOnly: false }))
|
||||
machineCustomFields.value = merged
|
||||
}
|
||||
|
||||
const setMachineCustomFieldValue = (field: AnyRecord, value: unknown) => {
|
||||
if (!field) return
|
||||
field.value = value
|
||||
if (field.customFieldValueId && (machine.value as AnyRecord)?.customFieldValues) {
|
||||
const stored = ((machine.value as AnyRecord).customFieldValues as AnyRecord[]).find(
|
||||
(fv) => fv.id === field.customFieldValueId,
|
||||
)
|
||||
if (stored) stored.value = value
|
||||
}
|
||||
}
|
||||
|
||||
const updateMachineCustomField = async (field: AnyRecord) => {
|
||||
if (!machine.value || !field) return
|
||||
|
||||
const { id: customFieldId, customFieldValueId } = field
|
||||
const fieldLabel = (field.name as string) || 'Champ personnalisé'
|
||||
|
||||
try {
|
||||
if (customFieldValueId) {
|
||||
const result: any = await updateCustomFieldValueApi(customFieldValueId as string, {
|
||||
value: field.value ?? '',
|
||||
} as any)
|
||||
if (result.success) {
|
||||
toast.showSuccess(`Champ "${fieldLabel}" de la machine mis à jour avec succès`)
|
||||
syncMachineCustomFields()
|
||||
} else {
|
||||
toast.showError(`Erreur lors de la mise à jour du champ "${fieldLabel}"`)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!customFieldId) {
|
||||
toast.showError(
|
||||
'Impossible de mettre à jour ce champ personnalisé (identifiant manquant).',
|
||||
)
|
||||
return
|
||||
}
|
||||
const result: any = await upsertCustomFieldValue(
|
||||
customFieldId as string,
|
||||
'machine',
|
||||
machine.value.id as string,
|
||||
field.value ?? '',
|
||||
)
|
||||
if (result.success) {
|
||||
const createdValue = result.data as AnyRecord
|
||||
toast.showSuccess(`Champ "${fieldLabel}" de la machine mis à jour avec succès`)
|
||||
if (createdValue?.id) {
|
||||
if (!createdValue.customField) {
|
||||
createdValue.customField = {
|
||||
id: customFieldId,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
required: field.required,
|
||||
options: field.options,
|
||||
}
|
||||
}
|
||||
field.customFieldValueId = createdValue.id
|
||||
field.readOnly = false
|
||||
const existingValues = Array.isArray(machine.value.customFieldValues)
|
||||
? (machine.value.customFieldValues as AnyRecord[]).filter(
|
||||
(item) => item.id !== createdValue.id,
|
||||
)
|
||||
: []
|
||||
machine.value.customFieldValues = [...existingValues, createdValue]
|
||||
}
|
||||
syncMachineCustomFields()
|
||||
} else {
|
||||
toast.showError(`Erreur lors de la mise à jour du champ "${fieldLabel}"`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour du champ personnalisé de la machine:', error)
|
||||
toast.showError(`Erreur lors de la mise à jour du champ "${fieldLabel}"`)
|
||||
}
|
||||
}
|
||||
|
||||
const updatePieceCustomField = async (fieldUpdate: AnyRecord) => {
|
||||
try {
|
||||
const result: any = await upsertCustomFieldValue(
|
||||
fieldUpdate.fieldId as string,
|
||||
'piece',
|
||||
fieldUpdate.pieceId as string,
|
||||
fieldUpdate.value,
|
||||
)
|
||||
if (result.success) {
|
||||
toast.showSuccess('Champ personnalisé mis à jour avec succès')
|
||||
} else {
|
||||
toast.showError('Erreur lors de la mise à jour du champ personnalisé')
|
||||
}
|
||||
} catch (error) {
|
||||
toast.showError('Erreur lors de la mise à jour du champ personnalisé')
|
||||
console.error('Erreur lors de la mise à jour du champ personnalisé:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
machineCustomFields,
|
||||
|
||||
// Computed
|
||||
visibleMachineCustomFields,
|
||||
|
||||
// Transform functions
|
||||
transformCustomFields,
|
||||
transformComponentCustomFields,
|
||||
|
||||
// Methods
|
||||
syncMachineCustomFields,
|
||||
setMachineCustomFieldValue,
|
||||
updateMachineCustomField,
|
||||
updatePieceCustomField,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user