feat: Composants d'affichage des machines et composants
- ComponentHierarchy.vue : Affichage hiérarchique des composants - ComponentItem.vue : Affichage d'un composant individuel - CustomFieldsDisplay.vue : Affichage des champs personnalisés - PieceItem.vue : Affichage des pièces de machines - Support de l'affichage en lecture seule et édition - Gestion des relations parent-enfant entre composants
This commit is contained in:
30
app/components/ComponentHierarchy.vue
Normal file
30
app/components/ComponentHierarchy.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Root Components -->
|
||||||
|
<div v-for="component in components" :key="component.id" class="border border-gray-200 rounded-lg p-4">
|
||||||
|
<ComponentItem
|
||||||
|
:component="component"
|
||||||
|
:is-edit-mode="isEditMode"
|
||||||
|
@update="$emit('update', $event)"
|
||||||
|
@edit-piece="$emit('edit-piece', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import ComponentItem from './ComponentItem.vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
components: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isEditMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['update', 'edit-piece'])
|
||||||
|
</script>
|
||||||
268
app/components/ComponentItem.vue
Normal file
268
app/components/ComponentItem.vue
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Component Header -->
|
||||||
|
<div class="flex items-center justify-between p-4 bg-base-200 rounded-lg">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold">{{ component.name }}</h3>
|
||||||
|
<div class="flex flex-wrap gap-2 mt-2">
|
||||||
|
<span v-if="component.reference" class="badge badge-outline badge-sm">
|
||||||
|
{{ component.reference }}
|
||||||
|
</span>
|
||||||
|
<span v-if="component.prestataire" class="badge badge-outline badge-sm">
|
||||||
|
{{ component.prestataire }}
|
||||||
|
</span>
|
||||||
|
<span v-if="component.emplacement" class="badge badge-outline badge-sm">
|
||||||
|
{{ component.emplacement }}
|
||||||
|
</span>
|
||||||
|
<span v-if="component.prix" class="badge badge-primary badge-sm">
|
||||||
|
{{ component.prix }}€
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Component Info Display - Editable or Read-only -->
|
||||||
|
<div class="p-4 bg-base-100 border border-gray-200 rounded-lg">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Nom</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
v-model="component.name"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
@blur="updateComponent"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ component.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Référence</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
v-model="component.reference"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
@blur="updateComponent"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ component.reference || 'Non définie' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Prestataire</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
v-model="component.prestataire"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
@blur="updateComponent"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ component.prestataire || 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Emplacement</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
v-model="component.emplacement"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
@blur="updateComponent"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ component.emplacement || 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Prix</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
v-model="component.prix"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
@blur="updateComponent"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ component.prix ? `${component.prix}€` : 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Fields Display - Editable or Read-only -->
|
||||||
|
<div v-if="component.customFields && component.customFields.length > 0" class="mt-4 pt-4 border-t border-gray-200">
|
||||||
|
<h4 class="font-semibold text-sm text-gray-700 mb-3">Champs personnalisés</h4>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div
|
||||||
|
v-for="field in component.customFields"
|
||||||
|
:key="field.id"
|
||||||
|
class="form-control"
|
||||||
|
>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text text-sm">{{ field.name }}</span>
|
||||||
|
<span v-if="field.required" class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Mode édition -->
|
||||||
|
<template v-if="isEditMode">
|
||||||
|
<!-- Champ de type TEXT -->
|
||||||
|
<input
|
||||||
|
v-if="field.type === 'text'"
|
||||||
|
v-model="field.value"
|
||||||
|
type="text"
|
||||||
|
:placeholder="field.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@blur="updateComponentCustomField(field)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type NUMBER -->
|
||||||
|
<input
|
||||||
|
v-else-if="field.type === 'number'"
|
||||||
|
v-model="field.value"
|
||||||
|
type="number"
|
||||||
|
:placeholder="field.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@blur="updateComponentCustomField(field)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type SELECT -->
|
||||||
|
<select
|
||||||
|
v-else-if="field.type === 'select'"
|
||||||
|
v-model="field.value"
|
||||||
|
class="select select-bordered select-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@change="updateComponentCustomField(field)"
|
||||||
|
>
|
||||||
|
<option value="">{{ field.defaultValue || 'Sélectionner...' }}</option>
|
||||||
|
<option
|
||||||
|
v-for="option in field.options"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Champ de type BOOLEAN -->
|
||||||
|
<div v-else-if="field.type === 'boolean'" class="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
v-model="field.value"
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
:checked="field.value === 'true'"
|
||||||
|
@change="updateComponentCustomField(field)"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">{{ field.value === 'true' ? 'Oui' : 'Non' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Champ de type DATE -->
|
||||||
|
<input
|
||||||
|
v-else-if="field.type === 'date'"
|
||||||
|
v-model="field.value"
|
||||||
|
type="date"
|
||||||
|
:placeholder="field.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@blur="updateComponentCustomField(field)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Mode lecture seule -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ field.value || field.defaultValue || 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Component Pieces -->
|
||||||
|
<div v-if="component.pieces && component.pieces.length > 0" class="space-y-2">
|
||||||
|
<h4 class="font-semibold text-gray-700">Pièces du composant</h4>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
|
<PieceItem
|
||||||
|
v-for="piece in component.pieces"
|
||||||
|
:key="piece.id"
|
||||||
|
:piece="piece"
|
||||||
|
:is-edit-mode="isEditMode"
|
||||||
|
@update="updatePiece"
|
||||||
|
@edit="editPiece"
|
||||||
|
@custom-field-update="updatePieceCustomField"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sub Components -->
|
||||||
|
<div v-if="component.subComponents && component.subComponents.length > 0" class="space-y-3">
|
||||||
|
<h4 class="font-semibold text-gray-700">Sous-composants</h4>
|
||||||
|
<div class="space-y-3 pl-4 border-l-2 border-gray-200">
|
||||||
|
<ComponentItem
|
||||||
|
v-for="subComponent in component.subComponents"
|
||||||
|
:key="subComponent.id"
|
||||||
|
:component="subComponent"
|
||||||
|
:is-edit-mode="isEditMode"
|
||||||
|
@update="$emit('update', $event)"
|
||||||
|
@edit-piece="$emit('edit-piece', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import PieceItem from './PieceItem.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
component: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isEditMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update', 'edit-piece'])
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const updateComponent = () => {
|
||||||
|
emit('update', props.component)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateComponentCustomField = (field) => {
|
||||||
|
// Mettre à jour le champ personnalisé du composant
|
||||||
|
emit('update', props.component)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePiece = (updatedPiece) => {
|
||||||
|
emit('edit-piece', updatedPiece)
|
||||||
|
}
|
||||||
|
|
||||||
|
const editPiece = (piece) => {
|
||||||
|
emit('edit-piece', piece)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePieceCustomField = (fieldUpdate) => {
|
||||||
|
// Forward to parent
|
||||||
|
emit('edit-piece', { ...fieldUpdate, type: 'custom-field-update' })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
135
app/components/CustomFieldsDisplay.vue
Normal file
135
app/components/CustomFieldsDisplay.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="customFields && customFields.length > 0" class="space-y-4">
|
||||||
|
<h4 class="font-semibold text-gray-700 mb-3">Champs personnalisés</h4>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div
|
||||||
|
v-for="field in customFields"
|
||||||
|
:key="field.id"
|
||||||
|
class="form-control"
|
||||||
|
>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">{{ field.name }}</span>
|
||||||
|
<span v-if="field.required" class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Champ de type TEXT -->
|
||||||
|
<input
|
||||||
|
v-if="field.type === 'text'"
|
||||||
|
v-model="fieldValues[field.id]"
|
||||||
|
type="text"
|
||||||
|
:placeholder="field.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@blur="updateCustomFieldValue(field.id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type NUMBER -->
|
||||||
|
<input
|
||||||
|
v-else-if="field.type === 'number'"
|
||||||
|
v-model="fieldValues[field.id]"
|
||||||
|
type="number"
|
||||||
|
:placeholder="field.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@blur="updateCustomFieldValue(field.id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type SELECT -->
|
||||||
|
<select
|
||||||
|
v-else-if="field.type === 'select'"
|
||||||
|
v-model="fieldValues[field.id]"
|
||||||
|
class="select select-bordered select-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@change="updateCustomFieldValue(field.id)"
|
||||||
|
>
|
||||||
|
<option value="">{{ field.defaultValue || 'Sélectionner...' }}</option>
|
||||||
|
<option
|
||||||
|
v-for="option in field.options"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Champ de type BOOLEAN -->
|
||||||
|
<div v-else-if="field.type === 'boolean'" class="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
v-model="fieldValues[field.id]"
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
:checked="fieldValues[field.id] === 'true'"
|
||||||
|
@change="updateCustomFieldValue(field.id)"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">{{ fieldValues[field.id] === 'true' ? 'Oui' : 'Non' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Champ de type DATE -->
|
||||||
|
<input
|
||||||
|
v-else-if="field.type === 'date'"
|
||||||
|
v-model="fieldValues[field.id]"
|
||||||
|
type="date"
|
||||||
|
:placeholder="field.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="field.required"
|
||||||
|
@blur="updateCustomFieldValue(field.id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
customFields: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
entityId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
entityType: {
|
||||||
|
type: String,
|
||||||
|
required: true, // 'machine', 'composant', 'piece'
|
||||||
|
validator: (value) => ['machine', 'composant', 'piece'].includes(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update'])
|
||||||
|
|
||||||
|
// Valeurs des champs personnalisés
|
||||||
|
const fieldValues = reactive({})
|
||||||
|
|
||||||
|
// Initialiser les valeurs avec les valeurs par défaut
|
||||||
|
const initializeFieldValues = () => {
|
||||||
|
props.customFields.forEach(field => {
|
||||||
|
if (!fieldValues[field.id]) {
|
||||||
|
fieldValues[field.id] = field.defaultValue || ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mettre à jour la valeur d'un champ personnalisé
|
||||||
|
const updateCustomFieldValue = (fieldId) => {
|
||||||
|
const field = props.customFields.find(f => f.id === fieldId)
|
||||||
|
if (field) {
|
||||||
|
emit('update', {
|
||||||
|
fieldId,
|
||||||
|
value: fieldValues[fieldId],
|
||||||
|
field
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surveiller les changements dans les champs personnalisés
|
||||||
|
watch(() => props.customFields, () => {
|
||||||
|
initializeFieldValues()
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initializeFieldValues()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
247
app/components/PieceItem.vue
Normal file
247
app/components/PieceItem.vue
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
<template>
|
||||||
|
<div class="border border-gray-200 rounded-lg p-4">
|
||||||
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<svg class="w-4 h-4 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="`piece-name-${piece.id}`"
|
||||||
|
v-model="pieceData.name"
|
||||||
|
type="text"
|
||||||
|
class="font-semibold text-lg input input-sm input-bordered"
|
||||||
|
@blur="updatePiece"
|
||||||
|
/>
|
||||||
|
<div v-else class="font-semibold text-lg input input-sm input-bordered bg-base-200">
|
||||||
|
{{ pieceData.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium">Référence:</span>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="`piece-reference-${piece.id}`"
|
||||||
|
v-model="pieceData.reference"
|
||||||
|
type="text"
|
||||||
|
class="input input-sm input-bordered ml-2"
|
||||||
|
@blur="updatePiece"
|
||||||
|
/>
|
||||||
|
<span v-else class="ml-2">{{ pieceData.reference || 'Non définie' }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-medium">Prestataire:</span>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="`piece-prestataire-${piece.id}`"
|
||||||
|
v-model="pieceData.prestataire"
|
||||||
|
type="text"
|
||||||
|
class="input input-sm input-bordered ml-2"
|
||||||
|
@blur="updatePiece"
|
||||||
|
/>
|
||||||
|
<span v-else class="ml-2">{{ pieceData.prestataire || 'Non défini' }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-medium">Emplacement:</span>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="`piece-emplacement-${piece.id}`"
|
||||||
|
v-model="pieceData.emplacement"
|
||||||
|
type="text"
|
||||||
|
class="input input-sm input-bordered ml-2"
|
||||||
|
@blur="updatePiece"
|
||||||
|
/>
|
||||||
|
<span v-else class="ml-2">{{ pieceData.emplacement || 'Non défini' }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="font-medium">Prix:</span>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="`piece-prix-${piece.id}`"
|
||||||
|
v-model="pieceData.prix"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
class="input input-sm input-bordered ml-2"
|
||||||
|
@blur="updatePiece"
|
||||||
|
/>
|
||||||
|
<span v-else class="ml-2">{{ pieceData.prix ? `${pieceData.prix}€` : 'Non défini' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Champs personnalisés de la pièce -->
|
||||||
|
<div v-if="piece.customFieldValues && piece.customFieldValues.length > 0" class="mt-4 pt-4 border-t border-gray-200">
|
||||||
|
<h5 class="text-sm font-medium text-gray-700 mb-3">Champs personnalisés</h5>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div
|
||||||
|
v-for="fieldValue in piece.customFieldValues"
|
||||||
|
:key="fieldValue.id"
|
||||||
|
class="form-control"
|
||||||
|
>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text text-sm">{{ fieldValue.customField.name }}</span>
|
||||||
|
<span v-if="fieldValue.customField.required" class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Mode édition -->
|
||||||
|
<template v-if="isEditMode">
|
||||||
|
<!-- Champ de type TEXT -->
|
||||||
|
<input
|
||||||
|
v-if="fieldValue.customField.type === 'text'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@input="setCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
type="text"
|
||||||
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateCustomFieldValue(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type NUMBER -->
|
||||||
|
<input
|
||||||
|
v-else-if="fieldValue.customField.type === 'number'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@input="setCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
type="number"
|
||||||
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateCustomFieldValue(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type SELECT -->
|
||||||
|
<select
|
||||||
|
v-else-if="fieldValue.customField.type === 'select'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@change="setCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
class="select select-bordered select-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateCustomFieldValue(fieldValue.id)"
|
||||||
|
>
|
||||||
|
<option value="">{{ fieldValue.customField.defaultValue || 'Sélectionner...' }}</option>
|
||||||
|
<option
|
||||||
|
v-for="option in fieldValue.customField.options"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Champ de type BOOLEAN -->
|
||||||
|
<div v-else-if="fieldValue.customField.type === 'boolean'" class="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@change="setCustomFieldValue(fieldValue.id, $event.target.checked ? 'true' : 'false')"
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
:checked="fieldValue.value === 'true'"
|
||||||
|
@blur="updateCustomFieldValue(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">{{ fieldValue.value === 'true' ? 'Oui' : 'Non' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Champ de type DATE -->
|
||||||
|
<input
|
||||||
|
v-else-if="fieldValue.customField.type === 'date'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@input="setCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
type="date"
|
||||||
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateCustomFieldValue(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Mode lecture seule -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ fieldValue.value || fieldValue.customField.defaultValue || 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, onMounted, watch } from 'vue'
|
||||||
|
import { useCustomFields } from '~/composables/useCustomFields'
|
||||||
|
import { useToast } from '~/composables/useToast'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
piece: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isEditMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update', 'edit', 'custom-field-update'])
|
||||||
|
|
||||||
|
// Données locales isolées pour cette pièce
|
||||||
|
const pieceData = reactive({
|
||||||
|
name: props.piece.name || '',
|
||||||
|
reference: props.piece.reference || '',
|
||||||
|
prestataire: props.piece.prestataire || '',
|
||||||
|
emplacement: props.piece.emplacement || '',
|
||||||
|
prix: props.piece.prix || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// Méthodes pour gérer les champs personnalisés
|
||||||
|
const setCustomFieldValue = (fieldValueId, value) => {
|
||||||
|
const fieldValue = props.piece.customFieldValues?.find(fv => fv.id === fieldValueId)
|
||||||
|
if (fieldValue) {
|
||||||
|
fieldValue.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePiece = () => {
|
||||||
|
const prixValue = pieceData.prix
|
||||||
|
emit('update', {
|
||||||
|
...props.piece,
|
||||||
|
...pieceData,
|
||||||
|
prix: prixValue && prixValue !== '' ? parseFloat(prixValue) : null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCustomFieldValue = async (fieldValueId) => {
|
||||||
|
const fieldValue = props.piece.customFieldValues?.find(fv => fv.id === fieldValueId)
|
||||||
|
if (fieldValue) {
|
||||||
|
const { updateCustomFieldValue } = useCustomFields()
|
||||||
|
const { showSuccess, showError } = useToast()
|
||||||
|
|
||||||
|
const result = await updateCustomFieldValue(fieldValueId, { value: fieldValue.value })
|
||||||
|
if (result.success) {
|
||||||
|
showSuccess(`Champ "${fieldValue.customField.name}" mis à jour avec succès`)
|
||||||
|
} else {
|
||||||
|
showError(`Erreur lors de la mise à jour du champ "${fieldValue.customField.name}"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Surveiller les changements dans les champs personnalisés
|
||||||
|
watch(() => props.piece.customFieldValues, () => {
|
||||||
|
console.log('PieceItem - customFieldValues updated:', props.piece.customFieldValues)
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Initialiser les données avec les props
|
||||||
|
pieceData.name = props.piece.name || ''
|
||||||
|
pieceData.reference = props.piece.reference || ''
|
||||||
|
pieceData.prestataire = props.piece.prestataire || ''
|
||||||
|
pieceData.emplacement = props.piece.emplacement || ''
|
||||||
|
pieceData.prix = props.piece.prix || ''
|
||||||
|
|
||||||
|
// Debug: vérifier si les champs personnalisés sont présents
|
||||||
|
console.log('PieceItem - piece:', props.piece)
|
||||||
|
console.log('PieceItem - customFieldValues:', props.piece.customFieldValues)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user