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

@@ -9,15 +9,15 @@
<div :class="layoutClass">
<div
v-for="(field, index) in fields"
:key="resolveFieldKey(field, index)"
:key="fieldKey(field, index)"
class="form-control"
>
<label class="label">
<span class="label-text text-sm">{{
resolveFieldName(field)
field.name
}}</span>
<span
v-if="resolveFieldRequired(field)"
v-if="field.required"
class="label-text-alt text-error"
>*</span>
</label>
@@ -26,32 +26,32 @@
<template v-if="isFieldEditable(field)">
<!-- Champ de type TEXT -->
<input
v-if="resolveFieldType(field) === 'text'"
v-if="field.type === 'text'"
:value="field.value ?? ''"
type="text"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
:required="field.required"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
<!-- Champ de type NUMBER -->
<input
v-else-if="resolveFieldType(field) === 'number'"
v-else-if="field.type === 'number'"
:value="field.value ?? ''"
type="number"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
:required="field.required"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
<!-- Champ de type SELECT -->
<select
v-else-if="resolveFieldType(field) === 'select'"
v-else-if="field.type === 'select'"
:value="field.value ?? ''"
class="select select-bordered select-sm"
:required="resolveFieldRequired(field)"
:required="field.required"
@change="onInput(field, ($event.target as HTMLSelectElement).value)"
@blur="onBlur(field)"
>
@@ -59,7 +59,7 @@
Sélectionner...
</option>
<option
v-for="option in resolveFieldOptions(field)"
v-for="option in field.options"
:key="option"
:value="option"
>
@@ -69,7 +69,7 @@
<!-- Champ de type BOOLEAN -->
<div
v-else-if="resolveFieldType(field) === 'boolean'"
v-else-if="field.type === 'boolean'"
class="flex items-center gap-2"
>
<input
@@ -85,21 +85,21 @@
<!-- Champ de type DATE -->
<input
v-else-if="resolveFieldType(field) === 'date'"
v-else-if="field.type === 'date'"
:value="field.value ?? ''"
type="date"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
:required="field.required"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
<!-- Champ de type TEXTAREA -->
<textarea
v-else-if="resolveFieldType(field) === 'textarea'"
v-else-if="field.type === 'textarea'"
:value="field.value ?? ''"
class="textarea textarea-bordered textarea-sm"
:required="resolveFieldRequired(field)"
:required="field.required"
@input="onInput(field, ($event.target as HTMLTextAreaElement).value)"
@blur="onBlur(field)"
/>
@@ -110,7 +110,7 @@
:value="field.value ?? ''"
type="text"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
:required="field.required"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
@@ -119,7 +119,7 @@
<!-- Mode lecture seule -->
<template v-else>
<div class="input input-bordered input-sm bg-base-200">
{{ formatFieldDisplayValue(field) }}
{{ formatValueForDisplay(field) }}
</div>
</template>
</div>
@@ -128,18 +128,10 @@
</template>
<script setup lang="ts">
import {
resolveFieldKey,
resolveFieldName,
resolveFieldType,
resolveFieldOptions,
resolveFieldRequired,
resolveFieldReadOnly,
formatFieldDisplayValue,
} from '~/shared/utils/entityCustomFieldLogic'
import { fieldKey, formatValueForDisplay, type CustomFieldInput } from '~/shared/utils/customFields'
const props = defineProps<{
fields: any[]
fields: CustomFieldInput[]
isEditMode: boolean
columns?: 1 | 2
title?: string
@@ -150,8 +142,8 @@ const props = defineProps<{
}>()
const emit = defineEmits<{
'field-input': [field: any, value: string]
'field-blur': [field: any]
'field-input': [field: CustomFieldInput, value: string]
'field-blur': [field: CustomFieldInput]
}>()
const layoutClass = computed(() =>
@@ -170,16 +162,16 @@ const containerClass = computed(() =>
const editable = computed(() => props.editable ?? true)
const emitBlur = computed(() => props.emitBlur ?? true)
function isFieldEditable(field: any) {
return props.isEditMode && editable.value && !resolveFieldReadOnly(field)
function isFieldEditable(field: CustomFieldInput) {
return props.isEditMode && editable.value && !field.readOnly
}
function onInput(field: any, value: string) {
function onInput(field: CustomFieldInput, value: string) {
field.value = value
emit('field-input', field, value)
}
function onBooleanChange(field: any, checked: boolean) {
function onBooleanChange(field: CustomFieldInput, checked: boolean) {
const value = checked ? 'true' : 'false'
field.value = value
emit('field-input', field, value)
@@ -188,7 +180,7 @@ function onBooleanChange(field: any, checked: boolean) {
}
}
function onBlur(field: any) {
function onBlur(field: CustomFieldInput) {
if (emitBlur.value) {
emit('field-blur', field)
}

View File

@@ -74,7 +74,7 @@
</template>
<script setup lang="ts">
import { fieldKey, type CustomFieldInput } from '~/shared/utils/customFieldFormUtils'
import { fieldKey, type CustomFieldInput } from '~/shared/utils/customFields'
defineProps<{
fields: CustomFieldInput[]