feat: replis hiérarchie composants et sections type
This commit is contained in:
@@ -1,125 +1,107 @@
|
||||
<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 class="flex items-start justify-between p-4 bg-base-200 rounded-lg">
|
||||
<div class="flex items-start gap-3 w-full">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-sm btn-circle shrink-0 transition-transform"
|
||||
:class="{ 'rotate-90': !isCollapsed }"
|
||||
@click="toggleCollapse"
|
||||
:aria-expanded="!isCollapsed"
|
||||
:title="isCollapsed ? 'Déplier les détails du composant' : 'Replier les détails du composant'"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<span class="sr-only">{{ isCollapsed ? 'Déplier' : 'Replier' }} le composant</span>
|
||||
</button>
|
||||
<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>
|
||||
</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 v-show="!isCollapsed" class="space-y-4">
|
||||
<!-- 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>
|
||||
<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 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>
|
||||
<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 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>
|
||||
<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 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>
|
||||
<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 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>
|
||||
</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"
|
||||
>
|
||||
<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
|
||||
<input
|
||||
v-if="field.type === 'text'"
|
||||
v-model="field.value"
|
||||
type="text"
|
||||
@@ -128,9 +110,7 @@
|
||||
:required="field.required"
|
||||
@blur="updateComponentCustomField(field)"
|
||||
/>
|
||||
|
||||
<!-- Champ de type NUMBER -->
|
||||
<input
|
||||
<input
|
||||
v-else-if="field.type === 'number'"
|
||||
v-model="field.value"
|
||||
type="number"
|
||||
@@ -139,9 +119,7 @@
|
||||
:required="field.required"
|
||||
@blur="updateComponentCustomField(field)"
|
||||
/>
|
||||
|
||||
<!-- Champ de type SELECT -->
|
||||
<select
|
||||
<select
|
||||
v-else-if="field.type === 'select'"
|
||||
v-model="field.value"
|
||||
class="select select-bordered select-sm"
|
||||
@@ -149,29 +127,20 @@
|
||||
@change="updateComponentCustomField(field)"
|
||||
>
|
||||
<option value="">{{ field.defaultValue || 'Sélectionner...' }}</option>
|
||||
<option
|
||||
v-for="option in field.options"
|
||||
:key="option"
|
||||
:value="option"
|
||||
>
|
||||
{{ option }}
|
||||
</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
|
||||
<input
|
||||
v-model="field.value"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm"
|
||||
:checked="field.value === 'true'"
|
||||
true-value="true"
|
||||
false-value="false"
|
||||
@change="updateComponentCustomField(field)"
|
||||
/>
|
||||
<span class="text-sm">{{ field.value === 'true' ? 'Oui' : 'Non' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Champ de type DATE -->
|
||||
<input
|
||||
<input
|
||||
v-else-if="field.type === 'date'"
|
||||
v-model="field.value"
|
||||
type="date"
|
||||
@@ -181,53 +150,51 @@
|
||||
@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>
|
||||
<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"
|
||||
/>
|
||||
<!-- 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>
|
||||
</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)"
|
||||
/>
|
||||
<!-- 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"
|
||||
:collapse-all="collapseAll"
|
||||
:toggle-token="toggleToken"
|
||||
@update="$emit('update', $event)"
|
||||
@edit-piece="$emit('edit-piece', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import PieceItem from './PieceItem.vue'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -238,11 +205,33 @@ const props = defineProps({
|
||||
isEditMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
collapseAll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
toggleToken: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update', 'edit-piece'])
|
||||
|
||||
const isCollapsed = ref(true)
|
||||
|
||||
watch(
|
||||
() => props.toggleToken,
|
||||
() => {
|
||||
isCollapsed.value = props.collapseAll
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
// Methods
|
||||
const updateComponent = () => {
|
||||
emit('update', props.component)
|
||||
@@ -265,4 +254,4 @@ const updatePieceCustomField = (fieldUpdate) => {
|
||||
// Forward to parent
|
||||
emit('edit-piece', { ...fieldUpdate, type: 'custom-field-update' })
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user