refactor(custom-fields) : unify 3 parallel implementations into 1 module

Replace ~2900 lines across 9 files with ~400 lines in 2 files:
- shared/utils/customFields.ts (types + pure helpers)
- composables/useCustomFieldInputs.ts (reactive composable)

Migrated all consumers:
- Backend: add defaultValue to API Platform serialization groups
- Standalone pages: component edit/create, piece edit/create, product edit/create/detail
- Machine page: MachineCustomFieldsCard, MachineInfoCard, useMachineDetailCustomFields
- Hierarchy: ComponentItem, PieceItem
- Shared: CustomFieldDisplay, CustomFieldInputGrid
- Category editor: componentStructure.ts

Deleted:
- entityCustomFieldLogic.ts (335 lines)
- customFieldUtils.ts (440 lines)
- customFieldFormUtils.ts (404 lines)
- useEntityCustomFields.ts (181 lines)
- customFieldFormUtils.test.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 13:09:27 +02:00
parent f2eff89e00
commit 894d522036
25 changed files with 861 additions and 2279 deletions

View File

@@ -6,7 +6,6 @@ import { usePieceTypes } from '~/composables/usePieceTypes'
import { useProductTypes } from '~/composables/useProductTypes'
import { usePieces } from '~/composables/usePieces'
import { useProducts } from '~/composables/useProducts'
import { useCustomFields } from '~/composables/useCustomFields'
import { useApi } from '~/composables/useApi'
import { useToast } from '~/composables/useToast'
import { extractRelationId } from '~/shared/apiRelations'
@@ -29,12 +28,7 @@ import {
import type { ComponentModelStructure } from '~/shared/types/inventory'
import type { ModelType } from '~/services/modelTypes'
import { canPreviewDocument } from '~/utils/documentPreview'
import {
type CustomFieldInput,
buildCustomFieldInputs,
requiredCustomFieldsFilled as _requiredCustomFieldsFilled,
saveCustomFieldValues as _saveCustomFieldValues,
} from '~/shared/utils/customFieldFormUtils'
import { useCustomFieldInputs, type CustomFieldInput } from '~/composables/useCustomFieldInputs'
import { collectStructureSelections } from '~/shared/utils/structureSelectionUtils'
interface ComponentCatalogType extends ModelType {
@@ -64,7 +58,6 @@ export function useComponentEdit(componentId: string) {
const { products } = useProducts()
const { ensureConstructeurs } = useConstructeurs()
const { fetchLinks, syncLinks } = useConstructeurLinks()
const { upsertCustomFieldValue, updateCustomFieldValue } = useCustomFields()
const toast = useToast()
const { loadDocumentsByComponent, uploadDocuments, deleteDocument } = useDocuments()
const {
@@ -96,7 +89,23 @@ export function useComponentEdit(componentId: string) {
const originalConstructeurLinks = ref<ConstructeurLinkEntry[]>([])
const constructeurIdsFromForm = computed(() => constructeurIdsFromLinks(constructeurLinks.value))
const customFieldInputs = ref<CustomFieldInput[]>([])
const {
fields: customFieldInputs,
requiredFilled: requiredCustomFieldsFilled,
saveAll: saveAllCustomFields,
refresh: refreshCustomFieldInputs,
} = useCustomFieldInputs({
definitions: computed(() => selectedTypeStructure.value?.customFields ?? []),
values: computed(() => component.value?.customFieldValues ?? []),
entityType: 'composant',
entityId: computed(() => component.value?.id ?? null),
onValueCreated: (newValue) => {
if (component.value && Array.isArray(component.value.customFieldValues)) {
component.value.customFieldValues.push(newValue)
}
},
})
const fetchedPieceTypeMap = ref<Record<string, string>>({})
const pieceTypeLabelMap = computed(() =>
buildTypeLabelMap(pieceTypes.value, fetchedPieceTypeMap.value),
@@ -207,19 +216,6 @@ export function useComponentEdit(componentId: string) {
return structure ? normalizeStructureForEditor(structure) : null
})
const refreshCustomFieldInputs = (
structureOverride?: ComponentModelStructure | null,
valuesOverride?: any[] | null,
) => {
const structure = structureOverride ?? selectedTypeStructure.value ?? null
const values = valuesOverride ?? component.value?.customFieldValues ?? null
customFieldInputs.value = buildCustomFieldInputs(structure, values)
}
const requiredCustomFieldsFilled = computed(() =>
_requiredCustomFieldsFilled(customFieldInputs.value),
)
const canSubmit = computed(() => Boolean(
canEdit.value
&& component.value
@@ -239,8 +235,7 @@ export function useComponentEdit(componentId: string) {
component.value = result.data
componentDocuments.value = Array.isArray(result.data?.documents) ? result.data.documents : []
const customValues = Array.isArray(result.data?.customFieldValues) ? result.data.customFieldValues : []
refreshCustomFieldInputs(undefined, customValues)
// The watcher on useCustomFieldInputs will auto-refresh when component.value changes
loadHistory(result.data.id).catch(() => {})
}
@@ -392,14 +387,10 @@ export function useComponentEdit(componentId: string) {
const result = await updateComposant(component.value.id, payload)
if (result.success && result.data) {
const updatedComponent = result.data as Record<string, any>
await _saveCustomFieldValues(
'composant',
updatedComponent.id,
[
updatedComponent?.typeComposant?.structure?.customFields,
],
{ customFieldInputs, upsertCustomFieldValue, updateCustomFieldValue, toast },
)
const failedFields = await saveAllCustomFields()
if (failedFields.length) {
toast.showError(`Erreur sur les champs : ${failedFields.join(', ')}`)
}
// Save slot edits
const slotPromises: Promise<any>[] = []
@@ -499,7 +490,7 @@ export function useComponentEdit(componentId: string) {
initialized.value = true
}
refreshCustomFieldInputs(selectedTypeStructure.value ?? currentStructure, currentComponent.customFieldValues)
// useCustomFieldInputs auto-refreshes via its watcher on definitions + values
},
{ immediate: true },
)