Files
Inventory/frontend/app/components/common/CustomFieldDisplay.vue
r-dev 0049638e3c fix(custom-fields) : context fields display, batch save, hierarchy propagation and UniqueEntity fix
- ComponentItem/PieceItem: DaisyUI divider, emit context-field-update for batch save
- CustomFieldDisplay: support editable/emit-blur/title/show-header props
- MachineComponentsCard/MachinePiecesCard: propagate custom-field-update events
- useMachineDetailCustomFields: pendingContextFieldUpdates + saveAllContextCustomFields
- useMachineDetailData: wire context field save into submitEdition
- useMachineDetailUpdates: only PATCH changed machine fields
- useMachineHierarchy: propagate contextCustomFields/Values from link to nodes
- componentStructure: include machineContextOnly in normalizeStructureForEditor
- Machine entity: convert empty reference to null, ignoreNull on UniqueEntity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:46:39 +02:00

197 lines
5.7 KiB
Vue

<template>
<div
v-if="fields.length"
:class="containerClass"
>
<h5 v-if="showHeader" class="text-sm font-medium text-base-content/80 mb-3">
{{ title }}
</h5>
<div :class="layoutClass">
<div
v-for="(field, index) in fields"
:key="resolveFieldKey(field, index)"
class="form-control"
>
<label class="label">
<span class="label-text text-sm">{{
resolveFieldName(field)
}}</span>
<span
v-if="resolveFieldRequired(field)"
class="label-text-alt text-error"
>*</span>
</label>
<!-- Mode édition -->
<template v-if="isFieldEditable(field)">
<!-- Champ de type TEXT -->
<input
v-if="resolveFieldType(field) === 'text'"
:value="field.value ?? ''"
type="text"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
<!-- Champ de type NUMBER -->
<input
v-else-if="resolveFieldType(field) === 'number'"
:value="field.value ?? ''"
type="number"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
<!-- Champ de type SELECT -->
<select
v-else-if="resolveFieldType(field) === 'select'"
:value="field.value ?? ''"
class="select select-bordered select-sm"
:required="resolveFieldRequired(field)"
@change="onInput(field, ($event.target as HTMLSelectElement).value)"
@blur="onBlur(field)"
>
<option value="">
Sélectionner...
</option>
<option
v-for="option in resolveFieldOptions(field)"
:key="option"
:value="option"
>
{{ option }}
</option>
</select>
<!-- Champ de type BOOLEAN -->
<div
v-else-if="resolveFieldType(field) === 'boolean'"
class="flex items-center gap-2"
>
<input
type="checkbox"
class="checkbox checkbox-sm"
:checked="String(field.value).toLowerCase() === 'true'"
@change="onBooleanChange(field, ($event.target as HTMLInputElement).checked)"
>
<span class="text-sm">{{
String(field.value).toLowerCase() === "true" ? "Oui" : "Non"
}}</span>
</div>
<!-- Champ de type DATE -->
<input
v-else-if="resolveFieldType(field) === 'date'"
:value="field.value ?? ''"
type="date"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
<!-- Champ de type TEXTAREA -->
<textarea
v-else-if="resolveFieldType(field) === 'textarea'"
:value="field.value ?? ''"
class="textarea textarea-bordered textarea-sm"
:required="resolveFieldRequired(field)"
@input="onInput(field, ($event.target as HTMLTextAreaElement).value)"
@blur="onBlur(field)"
/>
<!-- Fallback: input text -->
<input
v-else
:value="field.value ?? ''"
type="text"
class="input input-bordered input-sm"
:required="resolveFieldRequired(field)"
@input="onInput(field, ($event.target as HTMLInputElement).value)"
@blur="onBlur(field)"
>
</template>
<!-- Mode lecture seule -->
<template v-else>
<div class="input input-bordered input-sm bg-base-200">
{{ formatFieldDisplayValue(field) }}
</div>
</template>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {
resolveFieldKey,
resolveFieldName,
resolveFieldType,
resolveFieldOptions,
resolveFieldRequired,
resolveFieldReadOnly,
formatFieldDisplayValue,
} from '~/shared/utils/entityCustomFieldLogic'
const props = defineProps<{
fields: any[]
isEditMode: boolean
columns?: 1 | 2
title?: string
showHeader?: boolean
withTopBorder?: boolean
editable?: boolean
emitBlur?: boolean
}>()
const emit = defineEmits<{
'field-input': [field: any, value: string]
'field-blur': [field: any]
}>()
const layoutClass = computed(() =>
props.columns === 2
? 'grid grid-cols-1 md:grid-cols-2 gap-4'
: 'space-y-3',
)
const title = computed(() => props.title ?? 'Champs personnalisés')
const showHeader = computed(() => props.showHeader ?? true)
const containerClass = computed(() =>
props.withTopBorder === false
? ''
: 'mt-4 pt-4 border-t border-base-200',
)
const editable = computed(() => props.editable ?? true)
const emitBlur = computed(() => props.emitBlur ?? true)
function isFieldEditable(field: any) {
return props.isEditMode && editable.value && !resolveFieldReadOnly(field)
}
function onInput(field: any, value: string) {
field.value = value
emit('field-input', field, value)
}
function onBooleanChange(field: any, checked: boolean) {
const value = checked ? 'true' : 'false'
field.value = value
emit('field-input', field, value)
if (emitBlur.value) {
emit('field-blur', field)
}
}
function onBlur(field: any) {
if (emitBlur.value) {
emit('field-blur', field)
}
}
</script>