feat: refactor type management screens

This commit is contained in:
Matthieu
2025-09-17 08:18:28 +02:00
parent 4d2d35f360
commit d605f2418f
7 changed files with 2328 additions and 657 deletions

View File

@@ -2,156 +2,521 @@
<div class="border border-gray-300 rounded-lg p-4 bg-base-100"> <div class="border border-gray-300 rounded-lg p-4 bg-base-100">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleComponent"
title="Plier / déplier le composant"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': expanded }"
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"></path>
</svg>
</button>
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
</svg> </svg>
<h4 class="text-lg font-medium">{{ component.name }}</h4> <h4 class="text-lg font-medium">{{ component.name }}</h4>
<span v-if="!expanded && component.reference" class="text-xs text-gray-500">(Ref: {{ component.reference }})</span>
</div> </div>
<div class="text-sm text-gray-500"> <div v-if="!expanded && compactInfo" class="text-xs text-gray-500">
{{ component.reference ? `Ref: ${component.reference}` : '' }} {{ compactInfo }}
</div> </div>
</div> </div>
<!-- Informations du composant --> <template v-if="expanded">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div v-if="component.prestataire" class="text-sm"> <div v-if="component.prestataire" class="text-sm">
<span class="font-medium">Prestataire:</span> {{ component.prestataire }} <span class="font-medium">Prestataire:</span> {{ component.prestataire }}
</div> </div>
<div v-if="component.emplacement" class="text-sm"> <div v-if="component.emplacement" class="text-sm">
<span class="font-medium">Emplacement:</span> {{ component.emplacement }} <span class="font-medium">Emplacement:</span> {{ component.emplacement }}
</div> </div>
<div v-if="component.prix" class="text-sm"> <div v-if="component.prix" class="text-sm">
<span class="font-medium">Prix:</span> {{ component.prix }} <span class="font-medium">Prix:</span> {{ component.prix }}
</div>
</div>
<!-- Champs personnalisés du composant -->
<div v-if="component.customFields && component.customFields.length > 0" class="mb-4">
<h5 class="text-sm font-medium text-gray-700 mb-2">Champs personnalisés:</h5>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
<div v-for="field in component.customFields" :key="field.id" class="bg-gray-50 rounded p-2">
<div class="text-xs font-medium">{{ field.name }}</div>
<div class="text-xs text-gray-600">{{ field.type }}</div>
<div v-if="field.required" class="text-xs text-red-500">Obligatoire</div>
<div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div>
</div> </div>
</div> </div>
</div>
<!-- Pièces du composant --> <div v-if="component.customFields && component.customFields.length > 0" class="mb-4">
<div v-if="component.pieces && component.pieces.length > 0" class="mb-4"> <div class="flex items-center justify-between mb-2">
<h5 class="text-sm font-medium text-gray-700 mb-2">Pièces du composant:</h5> <h5 class="text-sm font-medium text-gray-700">Champs personnalisés</h5>
<div class="space-y-2"> <button
<div v-for="(piece, pieceIndex) in component.pieces" :key="pieceIndex" class="border border-gray-200 rounded p-3 bg-gray-50"> type="button"
<div class="flex items-center justify-between mb-2"> class="btn btn-ghost btn-xs p-1"
<div class="flex items-center gap-2"> @click="toggleSection('customFields')"
<svg class="w-4 h-4 text-red-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
</svg> class="w-3 h-3 transition-transform duration-200"
<span class="font-medium">{{ piece.name }}</span> :class="{ 'rotate-90': expandedSections.customFields }"
</div> fill="none"
<div class="text-xs text-gray-500"> stroke="currentColor"
{{ piece.reference ? `Ref: ${piece.reference}` : '' }} viewBox="0 0 24 24"
</div> >
</div> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
<!-- Informations de la pièce --> </button>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mb-2"> </div>
<div v-if="piece.prestataire" class="text-xs"> <div v-if="expandedSections.customFields" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
<span class="font-medium">Prestataire:</span> {{ piece.prestataire }} <div v-for="field in component.customFields" :key="field.id || field.name" class="bg-gray-50 rounded p-2">
</div> <div class="text-xs font-medium">{{ field.name }}</div>
<div v-if="piece.emplacement" class="text-xs"> <div class="text-xs text-gray-600">{{ field.type }}</div>
<span class="font-medium">Emplacement:</span> {{ piece.emplacement }} <div v-if="field.required" class="text-xs text-red-500">Obligatoire</div>
</div> <div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div>
<div v-if="piece.prix" class="text-xs">
<span class="font-medium">Prix:</span> {{ piece.prix }}
</div>
</div>
<!-- Champs personnalisés de la pièce -->
<div v-if="piece.customFields && piece.customFields.length > 0">
<div class="text-xs font-medium text-gray-600 mb-1">Champs personnalisés:</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-1">
<div v-for="field in piece.customFields" :key="field.id" class="bg-white rounded p-1">
<div class="text-xs font-medium">{{ field.name }}</div>
<div class="text-xs text-gray-600">{{ field.type }}</div>
<div v-if="field.required" class="text-xs text-red-500">Obligatoire</div>
<div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Sous-composants --> <div v-if="component.pieces && component.pieces.length > 0" class="mb-4">
<div v-if="component.subComponents && component.subComponents.length > 0" class="ml-4 border-l-2 border-gray-300 pl-4"> <div class="flex items-center justify-between mb-2">
<h5 class="text-sm font-medium text-gray-700 mb-2">Sous-composants:</h5> <h5 class="text-sm font-medium text-gray-700">Pièces du composant</h5>
<div class="space-y-3"> <button
<div v-for="(subComponent, subIndex) in component.subComponents" :key="subIndex" class="border border-gray-200 rounded p-3 bg-gray-50"> type="button"
<div class="flex items-center justify-between mb-2"> class="btn btn-ghost btn-xs p-1"
<div class="flex items-center gap-2"> @click="toggleSection('pieces')"
<svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> >
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> <svg
</svg> class="w-3 h-3 transition-transform duration-200"
<span class="font-medium">{{ subComponent.name }}</span> :class="{ 'rotate-90': expandedSections.pieces }"
</div> fill="none"
<div class="text-xs text-gray-500"> stroke="currentColor"
{{ subComponent.reference ? `Ref: ${subComponent.reference}` : '' }} viewBox="0 0 24 24"
</div> >
</div> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
<!-- Informations du sous-composant --> </button>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mb-2"> </div>
<div v-if="subComponent.prestataire" class="text-xs"> <div v-if="expandedSections.pieces" class="space-y-2">
<span class="font-medium">Prestataire:</span> {{ subComponent.prestataire }} <div
</div> v-for="(piece, pieceIndex) in component.pieces"
<div v-if="subComponent.emplacement" class="text-xs"> :key="pieceIndex"
<span class="font-medium">Emplacement:</span> {{ subComponent.emplacement }} class="border border-gray-200 rounded p-3 bg-gray-50"
</div> >
<div v-if="subComponent.prix" class="text-xs"> <div class="flex items-center justify-between mb-2">
<span class="font-medium">Prix:</span> {{ subComponent.prix }} <div class="flex items-center gap-2">
</div> <button
</div> type="button"
class="btn btn-ghost btn-xs p-1"
<!-- Champs personnalisés du sous-composant --> @click="togglePieceDetails(pieceIndex)"
<div v-if="subComponent.customFields && subComponent.customFields.length > 0" class="mb-2"> >
<div class="text-xs font-medium text-gray-600 mb-1">Champs personnalisés:</div> <svg
<div class="grid grid-cols-1 md:grid-cols-2 gap-1"> class="w-3 h-3 transition-transform duration-200"
<div v-for="field in subComponent.customFields" :key="field.id" class="bg-white rounded p-1"> :class="{ 'rotate-90': isPieceExpanded(pieceIndex) }"
<div class="text-xs font-medium">{{ field.name }}</div> fill="none"
<div class="text-xs text-gray-600">{{ field.type }}</div> stroke="currentColor"
<div v-if="field.required" class="text-xs text-red-500">Obligatoire</div> viewBox="0 0 24 24"
<div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div> >
</div> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</div> </svg>
</div> </button>
<svg class="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<!-- Pièces du sous-composant -->
<div v-if="subComponent.pieces && subComponent.pieces.length > 0">
<div class="text-xs font-medium text-gray-600 mb-1">Pièces:</div>
<div class="space-y-1">
<div v-for="(piece, pieceIndex) in subComponent.pieces" :key="pieceIndex" class="flex items-center gap-2">
<svg class="w-3 h-3 text-red-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> <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> </svg>
<span class="text-xs">{{ piece.name }}</span> <span class="font-medium">{{ piece.name }}</span>
<span v-if="piece.reference" class="text-xs text-gray-500">({{ piece.reference }})</span> <span v-if="!isPieceExpanded(pieceIndex) && piece.reference" class="text-xs text-gray-500">(Ref: {{ piece.reference }})</span>
<span v-if="piece.prix" class="text-xs text-green-600">{{ piece.prix }}</span> </div>
<div v-if="!isPieceExpanded(pieceIndex) && quickPieceSummary(piece)" class="text-xs text-gray-500">
{{ quickPieceSummary(piece) }}
</div>
</div>
<div v-if="isPieceExpanded(pieceIndex)">
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mb-2">
<div v-if="piece.prestataire" class="text-xs">
<span class="font-medium">Prestataire:</span> {{ piece.prestataire }}
</div>
<div v-if="piece.emplacement" class="text-xs">
<span class="font-medium">Emplacement:</span> {{ piece.emplacement }}
</div>
<div v-if="piece.prix" class="text-xs">
<span class="font-medium">Prix:</span> {{ piece.prix }}
</div>
</div>
<div v-if="piece.customFields && piece.customFields.length > 0">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-gray-600">Champs personnalisés</span>
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="togglePieceCustomFields(pieceIndex)"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isPieceCustomFieldSectionExpanded(pieceIndex) }"
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"></path>
</svg>
</button>
</div>
<div v-if="isPieceCustomFieldSectionExpanded(pieceIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-1">
<div v-for="field in piece.customFields" :key="field.id || field.name" class="bg-white rounded p-1">
<div class="text-xs font-medium">{{ field.name }}</div>
<div class="text-xs text-gray-600">{{ field.type }}</div>
<div v-if="field.required" class="text-xs text-red-500">Obligatoire</div>
<div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div v-if="component.subComponents && component.subComponents.length > 0" class="ml-4 border-l-2 border-gray-300 pl-4">
<div class="flex items-center justify-between mb-2">
<h5 class="text-sm font-medium text-gray-700">Sous-composants</h5>
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleSection('subComponents')"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': expandedSections.subComponents }"
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"></path>
</svg>
</button>
</div>
<div v-if="expandedSections.subComponents" class="space-y-3">
<div
v-for="(subComponent, subIndex) in component.subComponents"
:key="subIndex"
class="border border-gray-200 rounded p-3 bg-gray-50"
>
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleSubComponentDetails(subIndex)"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isSubComponentExpanded(subIndex) }"
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"></path>
</svg>
</button>
<svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span class="font-medium">{{ subComponent.name }}</span>
<span v-if="!isSubComponentExpanded(subIndex) && subComponent.reference" class="text-xs text-gray-500">(Ref: {{ subComponent.reference }})</span>
</div>
<div v-if="!isSubComponentExpanded(subIndex) && quickSubSummary(subComponent)" class="text-xs text-gray-500">
{{ quickSubSummary(subComponent) }}
</div>
</div>
<div v-if="isSubComponentExpanded(subIndex)" class="space-y-3">
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
<div v-if="subComponent.prestataire" class="text-xs">
<span class="font-medium">Prestataire:</span> {{ subComponent.prestataire }}
</div>
<div v-if="subComponent.emplacement" class="text-xs">
<span class="font-medium">Emplacement:</span> {{ subComponent.emplacement }}
</div>
<div v-if="subComponent.prix" class="text-xs">
<span class="font-medium">Prix:</span> {{ subComponent.prix }}
</div>
</div>
<div v-if="subComponent.customFields && subComponent.customFields.length > 0" class="mb-2">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-gray-600">Champs personnalisés</span>
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleSubComponentCustomFieldSection(subIndex)"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isSubComponentCustomFieldSectionExpanded(subIndex) }"
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"></path>
</svg>
</button>
</div>
<div v-if="isSubComponentCustomFieldSectionExpanded(subIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-1">
<div v-for="field in subComponent.customFields" :key="field.id || field.name" class="bg-white rounded p-1">
<div class="text-xs font-medium">{{ field.name }}</div>
<div class="text-xs text-gray-600">{{ field.type }}</div>
<div v-if="field.required" class="text-xs text-red-500">Obligatoire</div>
<div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div>
</div>
</div>
</div>
<div v-if="subComponent.pieces && subComponent.pieces.length > 0">
<div class="flex items-center justify-between mb-1">
<span class="text-xs text-gray-600 font-medium">Pièces</span>
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleSubComponentPieceSection(subIndex)"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isSubComponentPieceSectionExpanded(subIndex) }"
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"></path>
</svg>
</button>
</div>
<div v-if="isSubComponentPieceSectionExpanded(subIndex)" class="space-y-1">
<div
v-for="(piece, pieceIndex) in subComponent.pieces"
:key="pieceIndex"
class="border border-gray-100 rounded p-2 bg-white"
>
<div class="flex items-center justify-between mb-1">
<div class="flex items-center gap-1">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleSubPieceDetails(subIndex, pieceIndex)"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isSubPieceExpanded(subIndex, pieceIndex) }"
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"></path>
</svg>
</button>
<svg class="w-3 h-3 text-red-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>
<span class="text-xs font-medium">{{ piece.name }}</span>
<span v-if="!isSubPieceExpanded(subIndex, pieceIndex) && piece.reference" class="text-[10px] text-gray-500">(Ref: {{ piece.reference }})</span>
</div>
<div v-if="!isSubPieceExpanded(subIndex, pieceIndex) && quickPieceSummary(piece)" class="text-[10px] text-gray-500">
{{ quickPieceSummary(piece) }}
</div>
</div>
<div v-if="isSubPieceExpanded(subIndex, pieceIndex)">
<div class="grid grid-cols-2 gap-1 text-[11px] mb-1">
<div v-if="piece.prestataire">
<span class="font-medium">Prestataire:</span> {{ piece.prestataire }}
</div>
<div v-if="piece.emplacement">
<span class="font-medium">Emplacement:</span> {{ piece.emplacement }}
</div>
<div v-if="piece.prix">
<span class="font-medium">Prix:</span> {{ piece.prix }}
</div>
</div>
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-1">
<div class="flex items-center justify-between mb-1">
<span class="text-[11px] font-medium text-gray-600">Champs personnalisés</span>
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleSubPieceCustomFields(subIndex, pieceIndex)"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isSubPieceCustomFieldSectionExpanded(subIndex, pieceIndex) }"
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"></path>
</svg>
</button>
</div>
<div v-if="isSubPieceCustomFieldSectionExpanded(subIndex, pieceIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-1">
<div v-for="field in piece.customFields" :key="field.id || field.name" class="bg-gray-50 rounded p-1">
<div class="text-[11px] font-medium">{{ field.name }}</div>
<div class="text-[10px] text-gray-600">{{ field.type }}</div>
<div v-if="field.required" class="text-[10px] text-red-500">Obligatoire</div>
<div v-if="field.defaultValue" class="text-[10px] text-gray-500">Défaut: {{ field.defaultValue }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</div> </div>
</template> </template>
<script setup> <script setup>
defineProps({ import { ref, reactive, computed, watch, onMounted } from 'vue'
const props = defineProps({
component: { component: {
type: Object, type: Object,
required: true required: true
},
globalExpandState: {
type: Object,
default: null
} }
}) })
</script>
const expanded = ref(true)
const expandedSections = reactive({
customFields: true,
pieces: true,
subComponents: true
})
const expandedPieces = ref([])
const expandedPieceCustomFields = ref([])
const expandedSubComponents = ref([])
const expandedSubComponentCustomFields = ref([])
const expandedSubComponentPieces = ref([])
const expandedSubComponentPieceCustomFields = reactive({})
const compactInfo = computed(() => {
const infos = []
if (props.component?.prestataire) {
infos.push(props.component.prestataire)
}
if (props.component?.emplacement) {
infos.push(props.component.emplacement)
}
return infos.join(' • ')
})
const quickPieceSummary = (piece) => {
const infos = []
if (piece?.prestataire) infos.push(piece.prestataire)
if (piece?.emplacement) infos.push(piece.emplacement)
return infos.join(' • ')
}
const quickSubSummary = (subComponent) => {
const infos = []
if (subComponent?.prestataire) infos.push(subComponent.prestataire)
if (subComponent?.emplacement) infos.push(subComponent.emplacement)
return infos.join(' • ')
}
const applyGlobalExpansion = (expand) => {
expanded.value = expand
expandedSections.customFields = expand && (props.component?.customFields || []).length > 0
expandedSections.pieces = expand && (props.component?.pieces || []).length > 0
expandedSections.subComponents = expand && (props.component?.subComponents || []).length > 0
expandedPieces.value = (props.component?.pieces || []).map(() => expand)
expandedPieceCustomFields.value = (props.component?.pieces || []).map(() => expand)
expandedSubComponents.value = (props.component?.subComponents || []).map(() => expand)
expandedSubComponentCustomFields.value = (props.component?.subComponents || []).map(() => expand)
expandedSubComponentPieces.value = (props.component?.subComponents || []).map(() => expand)
Object.keys(expandedSubComponentPieceCustomFields).forEach((key) => delete expandedSubComponentPieceCustomFields[key])
;(props.component?.subComponents || []).forEach((subComponent, subIndex) => {
expandedSubComponentPieceCustomFields[subIndex] = (subComponent.pieces || []).map(() => ({
expanded: expand,
customFields: expand
}))
})
}
const initializeExpansionState = () => {
const initialExpand = props.globalExpandState ? props.globalExpandState.expanded : true
applyGlobalExpansion(initialExpand)
}
watch(() => props.component, () => {
initializeExpansionState()
}, { deep: true })
onMounted(() => {
initializeExpansionState()
})
const toggleComponent = () => {
expanded.value = !expanded.value
}
const toggleSection = (section) => {
expandedSections[section] = !expandedSections[section]
}
const isPieceExpanded = (index) => expandedPieces.value[index]
const togglePieceDetails = (index) => {
expandedPieces.value[index] = !expandedPieces.value[index]
}
const isPieceCustomFieldSectionExpanded = (index) => expandedPieceCustomFields.value[index]
const togglePieceCustomFields = (index) => {
expandedPieceCustomFields.value[index] = !expandedPieceCustomFields.value[index]
}
const isSubComponentExpanded = (index) => expandedSubComponents.value[index]
const toggleSubComponentDetails = (index) => {
expandedSubComponents.value[index] = !expandedSubComponents.value[index]
}
const isSubComponentCustomFieldSectionExpanded = (index) => expandedSubComponentCustomFields.value[index]
const toggleSubComponentCustomFieldSection = (index) => {
expandedSubComponentCustomFields.value[index] = !expandedSubComponentCustomFields.value[index]
}
const isSubComponentPieceSectionExpanded = (index) => expandedSubComponentPieces.value[index]
const toggleSubComponentPieceSection = (index) => {
expandedSubComponentPieces.value[index] = !expandedSubComponentPieces.value[index]
}
const isSubPieceExpanded = (subIndex, pieceIndex) => expandedSubComponentPieceCustomFields[subIndex]?.[pieceIndex]?.expanded ?? false
const ensureSubPieceEntry = (subIndex, pieceIndex) => {
if (!expandedSubComponentPieceCustomFields[subIndex]) {
expandedSubComponentPieceCustomFields[subIndex] = []
}
if (!expandedSubComponentPieceCustomFields[subIndex][pieceIndex]) {
expandedSubComponentPieceCustomFields[subIndex][pieceIndex] = { expanded: false, customFields: false }
}
}
const toggleSubPieceDetails = (subIndex, pieceIndex) => {
ensureSubPieceEntry(subIndex, pieceIndex)
expandedSubComponentPieceCustomFields[subIndex][pieceIndex].expanded = !expandedSubComponentPieceCustomFields[subIndex][pieceIndex].expanded
}
const isSubPieceCustomFieldSectionExpanded = (subIndex, pieceIndex) => expandedSubComponentPieceCustomFields[subIndex]?.[pieceIndex]?.customFields ?? false
const toggleSubPieceCustomFields = (subIndex, pieceIndex) => {
ensureSubPieceEntry(subIndex, pieceIndex)
expandedSubComponentPieceCustomFields[subIndex][pieceIndex].customFields = !expandedSubComponentPieceCustomFields[subIndex][pieceIndex].customFields
}
watch(() => props.globalExpandState?.id, () => {
if (props.globalExpandState) {
applyGlobalExpansion(props.globalExpandState.expanded)
}
})
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,36 @@
<template> <template>
<div class="space-y-6"> <div class="space-y-6">
<div class="flex justify-end">
<button
type="button"
class="btn btn-outline btn-sm"
@click="toggleAllSections"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
v-if="allExpanded"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 12H6"
></path>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v12m6-6H6"
></path>
</svg>
{{ allExpanded ? 'Tout plier' : 'Tout déplier' }}
</button>
</div>
<!-- Informations de base du type --> <!-- Informations de base du type -->
<div class="card bg-base-100 shadow-lg"> <div class="card bg-base-100 shadow-lg">
<div class="card-body"> <div class="card-body">
@@ -101,10 +132,29 @@
> >
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleCustomFieldDetails(fieldIndex)"
title="Plier / déplier le champ"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': isCustomFieldExpanded(fieldIndex) }"
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"></path>
</svg>
</button>
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 text-blue-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> <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> </svg>
<h5 class="text-sm font-medium">Champ personnalisé {{ fieldIndex + 1 }}</h5> <h5 class="text-sm font-medium">Champ personnalisé {{ fieldIndex + 1 }}</h5>
<span v-if="!isCustomFieldExpanded(fieldIndex)" class="text-xs text-gray-500 truncate max-w-[160px]">
{{ field.name || 'Sans nom' }}
</span>
</div> </div>
<button <button
type="button" type="button"
@@ -118,7 +168,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div v-if="isCustomFieldExpanded(fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label"> <label class="label">
<span class="label-text">Nom du champ</span> <span class="label-text">Nom du champ</span>
@@ -149,7 +199,7 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-3"> <div v-if="isCustomFieldExpanded(fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<input <input
v-model="field.required" v-model="field.required"
@@ -173,7 +223,7 @@
</div> </div>
<!-- Options pour les champs de type SELECT --> <!-- Options pour les champs de type SELECT -->
<div v-if="field.type === 'select'" class="mt-3"> <div v-if="isCustomFieldExpanded(fieldIndex) && field.type === 'select'" class="mt-3">
<label class="label"> <label class="label">
<span class="label-text">Options de la liste</span> <span class="label-text">Options de la liste</span>
<span class="label-text-alt">Une option par ligne</span> <span class="label-text-alt">Une option par ligne</span>
@@ -233,10 +283,29 @@
> >
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="togglePieceDetails(pieceIndex)"
title="Plier / déplier la pièce"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': isPieceExpanded(pieceIndex) }"
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"></path>
</svg>
</button>
<svg class="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 text-red-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> <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> </svg>
<h5 class="text-sm font-medium">Pièce {{ pieceIndex + 1 }}</h5> <h5 class="text-sm font-medium">Pièce {{ pieceIndex + 1 }}</h5>
<span v-if="!isPieceExpanded(pieceIndex)" class="text-xs text-gray-500 truncate max-w-[160px]">
{{ piece.name || 'Sans nom' }}
</span>
</div> </div>
<button <button
type="button" type="button"
@@ -250,7 +319,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div v-if="isPieceExpanded(pieceIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label"> <label class="label">
<span class="label-text">Nom de la pièce</span> <span class="label-text">Nom de la pièce</span>
@@ -303,7 +372,7 @@
</div> </div>
<!-- Champs personnalisés de la pièce --> <!-- Champs personnalisés de la pièce -->
<div class="mt-4"> <div v-if="isPieceExpanded(pieceIndex)" class="mt-4">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<span class="text-sm font-medium">Champs personnalisés de cette pièce :</span> <span class="text-sm font-medium">Champs personnalisés de cette pièce :</span>
<button <button
@@ -325,7 +394,28 @@
class="border border-gray-200 rounded p-2 bg-white" class="border border-gray-200 rounded p-2 bg-white"
> >
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<span class="text-xs font-medium">Champ {{ fieldIndex + 1 }}</span> <div class="flex items-center gap-1">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="togglePieceCustomFieldDetails(pieceIndex, fieldIndex)"
title="Plier / déplier le champ"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isPieceCustomFieldExpanded(pieceIndex, fieldIndex) }"
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"></path>
</svg>
</button>
<span class="text-xs font-medium">Champ {{ fieldIndex + 1 }}</span>
<span v-if="!isPieceCustomFieldExpanded(pieceIndex, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[120px]">
{{ field.name || 'Sans nom' }}
</span>
</div>
<button <button
type="button" type="button"
@click="removePieceCustomField(pieceIndex, fieldIndex)" @click="removePieceCustomField(pieceIndex, fieldIndex)"
@@ -337,7 +427,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2"> <div v-if="isPieceCustomFieldExpanded(pieceIndex, fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-2">
<input <input
v-model="field.name" v-model="field.name"
type="text" type="text"
@@ -354,7 +444,7 @@
</select> </select>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2"> <div v-if="isPieceCustomFieldExpanded(pieceIndex, fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<input <input
v-model="field.required" v-model="field.required"
@@ -372,7 +462,7 @@
</div> </div>
<!-- Options pour les champs de type SELECT --> <!-- Options pour les champs de type SELECT -->
<div v-if="field.type === 'select'" class="mt-2"> <div v-if="isPieceCustomFieldExpanded(pieceIndex, fieldIndex) && field.type === 'select'" class="mt-2">
<textarea <textarea
v-model="field.optionsText" v-model="field.optionsText"
placeholder="Option 1&#10;Option 2&#10;Option 3" placeholder="Option 1&#10;Option 2&#10;Option 3"
@@ -431,10 +521,29 @@
> >
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleComponentDetails(componentIndex)"
title="Plier / déplier le composant"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': isComponentExpanded(componentIndex) }"
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"></path>
</svg>
</button>
<svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg> </svg>
<h5 class="text-sm font-medium">Composant {{ componentIndex + 1 }}</h5> <h5 class="text-sm font-medium">Composant {{ componentIndex + 1 }}</h5>
<span v-if="!isComponentExpanded(componentIndex)" class="text-xs text-gray-500 truncate max-w-[160px]">
{{ component.name || 'Sans nom' }}
</span>
</div> </div>
<button <button
type="button" type="button"
@@ -448,7 +557,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div v-if="isComponentExpanded(componentIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label"> <label class="label">
<span class="label-text">Nom du composant</span> <span class="label-text">Nom du composant</span>
@@ -501,7 +610,7 @@
</div> </div>
<!-- Champs personnalisés du composant --> <!-- Champs personnalisés du composant -->
<div class="mt-4"> <div v-if="isComponentExpanded(componentIndex)" class="mt-4">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<span class="text-sm font-medium">Champs personnalisés du composant :</span> <span class="text-sm font-medium">Champs personnalisés du composant :</span>
<button <button
@@ -523,7 +632,28 @@
class="border border-gray-200 rounded p-2 bg-white" class="border border-gray-200 rounded p-2 bg-white"
> >
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<span class="text-xs font-medium">Champ {{ fieldIndex + 1 }}</span> <div class="flex items-center gap-1">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleComponentCustomFieldDetails(componentIndex, fieldIndex)"
title="Plier / déplier le champ"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isComponentCustomFieldExpanded(componentIndex, fieldIndex) }"
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"></path>
</svg>
</button>
<span class="text-xs font-medium">Champ {{ fieldIndex + 1 }}</span>
<span v-if="!isComponentCustomFieldExpanded(componentIndex, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[120px]">
{{ field.name || 'Sans nom' }}
</span>
</div>
<button <button
type="button" type="button"
@click="removeComponentCustomField(componentIndex, fieldIndex)" @click="removeComponentCustomField(componentIndex, fieldIndex)"
@@ -535,7 +665,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2"> <div v-if="isComponentCustomFieldExpanded(componentIndex, fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-2">
<input <input
v-model="field.name" v-model="field.name"
type="text" type="text"
@@ -552,7 +682,7 @@
</select> </select>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2"> <div v-if="isComponentCustomFieldExpanded(componentIndex, fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<input <input
v-model="field.required" v-model="field.required"
@@ -570,7 +700,7 @@
</div> </div>
<!-- Options pour les champs de type SELECT --> <!-- Options pour les champs de type SELECT -->
<div v-if="field.type === 'select'" class="mt-2"> <div v-if="isComponentCustomFieldExpanded(componentIndex, fieldIndex) && field.type === 'select'" class="mt-2">
<textarea <textarea
v-model="field.optionsText" v-model="field.optionsText"
placeholder="Option 1&#10;Option 2&#10;Option 3" placeholder="Option 1&#10;Option 2&#10;Option 3"
@@ -606,15 +736,40 @@
> >
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleComponentPieceDetails(componentIndex, pieceIndex)"
title="Plier / déplier la pièce"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isComponentPieceExpanded(componentIndex, pieceIndex) }"
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"></path>
</svg>
</button>
<svg class="w-3 h-3 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-3 h-3 text-red-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> <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> </svg>
<input <span class="text-xs font-medium">Pièce {{ pieceIndex + 1 }}</span>
v-model="piece.name" <template v-if="isComponentPieceExpanded(componentIndex, pieceIndex)">
type="text" <input
:placeholder="`Pièce ${pieceIndex + 1}`" v-model="piece.name"
class="input input-bordered input-xs flex-1" type="text"
/> :placeholder="`Nom de la pièce ${pieceIndex + 1}`"
class="input input-bordered input-xs flex-1"
/>
</template>
<span
v-else
class="text-[10px] text-gray-500 truncate max-w-[120px]"
>
{{ piece.name || 'Sans nom' }}
</span>
</div> </div>
<div class="flex gap-1"> <div class="flex gap-1">
<button <button
@@ -641,7 +796,7 @@
</div> </div>
<!-- Champs personnalisés de cette pièce --> <!-- Champs personnalisés de cette pièce -->
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-2 ml-4"> <div v-if="isComponentPieceExpanded(componentIndex, pieceIndex) && piece.customFields && piece.customFields.length > 0" class="mt-2 ml-4">
<div class="space-y-1"> <div class="space-y-1">
<div <div
v-for="(field, fieldIndex) in piece.customFields" v-for="(field, fieldIndex) in piece.customFields"
@@ -649,7 +804,28 @@
class="border border-gray-100 rounded p-1 bg-gray-50" class="border border-gray-100 rounded p-1 bg-gray-50"
> >
<div class="flex items-center justify-between mb-1"> <div class="flex items-center justify-between mb-1">
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span> <div class="flex items-center gap-1">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleComponentPieceCustomFieldDetails(componentIndex, pieceIndex, fieldIndex)"
title="Plier / déplier le champ"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex) }"
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"></path>
</svg>
</button>
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
<span v-if="!isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[100px]">
{{ field.name || 'Sans nom' }}
</span>
</div>
<button <button
type="button" type="button"
@click="removePieceCustomFieldFromComponent(componentIndex, pieceIndex, fieldIndex)" @click="removePieceCustomFieldFromComponent(componentIndex, pieceIndex, fieldIndex)"
@@ -661,7 +837,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-2 gap-1"> <div v-if="isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex)" class="grid grid-cols-2 gap-1">
<input <input
v-model="field.name" v-model="field.name"
type="text" type="text"
@@ -678,7 +854,7 @@
</select> </select>
</div> </div>
<div class="grid grid-cols-2 gap-1 mt-1"> <div v-if="isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex)" class="grid grid-cols-2 gap-1 mt-1">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<input <input
v-model="field.required" v-model="field.required"
@@ -696,7 +872,7 @@
</div> </div>
<!-- Options pour les champs de type SELECT --> <!-- Options pour les champs de type SELECT -->
<div v-if="field.type === 'select'" class="mt-1"> <div v-if="isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex) && field.type === 'select'" class="mt-1">
<textarea <textarea
v-model="field.optionsText" v-model="field.optionsText"
placeholder="Option 1&#10;Option 2&#10;Option 3" placeholder="Option 1&#10;Option 2&#10;Option 3"
@@ -738,7 +914,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, watch } from 'vue' import { ref, reactive, watch, onMounted } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@@ -755,11 +931,21 @@ const emit = defineEmits(['update:modelValue', 'submit'])
// État des sections dépliées/repliées // État des sections dépliées/repliées
const expandedSections = reactive({ const expandedSections = reactive({
customFields: true, customFields: false,
machinePieces: true, machinePieces: false,
components: true components: false
}) })
// État de dépliage pour chaque élément de l'arborescence
const allExpanded = ref(false)
const expandedCustomFields = ref([])
const expandedMachinePieces = ref([])
const expandedPieceCustomFields = reactive({})
const expandedComponents = ref([])
const expandedComponentCustomFields = reactive({})
const expandedComponentPieces = reactive({})
const expandedComponentPieceCustomFields = reactive({})
// Initialiser les données du formulaire // Initialiser les données du formulaire
const formData = reactive({ const formData = reactive({
name: props.modelValue?.name || '', name: props.modelValue?.name || '',
@@ -779,8 +965,189 @@ watch(formData, (newValue) => {
// Méthode pour basculer l'état d'une section // Méthode pour basculer l'état d'une section
const toggleSection = (sectionName) => { const toggleSection = (sectionName) => {
expandedSections[sectionName] = !expandedSections[sectionName] expandedSections[sectionName] = !expandedSections[sectionName]
if (!expandedSections[sectionName]) {
allExpanded.value = false
}
} }
const isCustomFieldExpanded = (index) => {
if (expandedCustomFields.value[index] === undefined) {
expandedCustomFields.value[index] = allExpanded.value
}
return expandedCustomFields.value[index]
}
const toggleCustomFieldDetails = (index) => {
expandedCustomFields.value[index] = !isCustomFieldExpanded(index)
if (!expandedCustomFields.value[index]) {
allExpanded.value = false
}
}
const ensurePieceFieldState = (pieceIndex) => {
if (!expandedPieceCustomFields[pieceIndex]) {
expandedPieceCustomFields[pieceIndex] = []
}
}
const isPieceExpanded = (index) => {
if (expandedMachinePieces.value[index] === undefined) {
expandedMachinePieces.value[index] = allExpanded.value
}
return expandedMachinePieces.value[index]
}
const togglePieceDetails = (index) => {
expandedMachinePieces.value[index] = !isPieceExpanded(index)
if (!expandedMachinePieces.value[index]) {
allExpanded.value = false
}
}
const isPieceCustomFieldExpanded = (pieceIndex, fieldIndex) => {
ensurePieceFieldState(pieceIndex)
if (expandedPieceCustomFields[pieceIndex][fieldIndex] === undefined) {
expandedPieceCustomFields[pieceIndex][fieldIndex] = allExpanded.value
}
return expandedPieceCustomFields[pieceIndex][fieldIndex]
}
const togglePieceCustomFieldDetails = (pieceIndex, fieldIndex) => {
ensurePieceFieldState(pieceIndex)
expandedPieceCustomFields[pieceIndex][fieldIndex] = !isPieceCustomFieldExpanded(pieceIndex, fieldIndex)
if (!expandedPieceCustomFields[pieceIndex][fieldIndex]) {
allExpanded.value = false
}
}
const ensureComponentState = (componentIndex) => {
if (!expandedComponentCustomFields[componentIndex]) {
expandedComponentCustomFields[componentIndex] = []
}
if (!expandedComponentPieces[componentIndex]) {
expandedComponentPieces[componentIndex] = []
}
if (!expandedComponentPieceCustomFields[componentIndex]) {
expandedComponentPieceCustomFields[componentIndex] = {}
}
}
const isComponentExpanded = (index) => {
if (expandedComponents.value[index] === undefined) {
expandedComponents.value[index] = allExpanded.value
}
return expandedComponents.value[index]
}
const toggleComponentDetails = (index) => {
expandedComponents.value[index] = !isComponentExpanded(index)
if (!expandedComponents.value[index]) {
allExpanded.value = false
}
}
const isComponentCustomFieldExpanded = (componentIndex, fieldIndex) => {
ensureComponentState(componentIndex)
if (expandedComponentCustomFields[componentIndex][fieldIndex] === undefined) {
expandedComponentCustomFields[componentIndex][fieldIndex] = allExpanded.value
}
return expandedComponentCustomFields[componentIndex][fieldIndex]
}
const toggleComponentCustomFieldDetails = (componentIndex, fieldIndex) => {
ensureComponentState(componentIndex)
expandedComponentCustomFields[componentIndex][fieldIndex] = !isComponentCustomFieldExpanded(componentIndex, fieldIndex)
if (!expandedComponentCustomFields[componentIndex][fieldIndex]) {
allExpanded.value = false
}
}
const ensureComponentPieceState = (componentIndex, pieceIndex) => {
ensureComponentState(componentIndex)
if (!expandedComponentPieceCustomFields[componentIndex][pieceIndex]) {
expandedComponentPieceCustomFields[componentIndex][pieceIndex] = []
}
}
const isComponentPieceExpanded = (componentIndex, pieceIndex) => {
ensureComponentState(componentIndex)
if (expandedComponentPieces[componentIndex][pieceIndex] === undefined) {
expandedComponentPieces[componentIndex][pieceIndex] = allExpanded.value
}
return expandedComponentPieces[componentIndex][pieceIndex]
}
const toggleComponentPieceDetails = (componentIndex, pieceIndex) => {
ensureComponentState(componentIndex)
expandedComponentPieces[componentIndex][pieceIndex] = !isComponentPieceExpanded(componentIndex, pieceIndex)
if (!expandedComponentPieces[componentIndex][pieceIndex]) {
allExpanded.value = false
}
}
const isComponentPieceCustomFieldExpanded = (componentIndex, pieceIndex, fieldIndex) => {
ensureComponentPieceState(componentIndex, pieceIndex)
if (expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex] === undefined) {
expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex] = allExpanded.value
}
return expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex]
}
const toggleComponentPieceCustomFieldDetails = (componentIndex, pieceIndex, fieldIndex) => {
ensureComponentPieceState(componentIndex, pieceIndex)
expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex] = !isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex)
if (!expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex]) {
allExpanded.value = false
}
}
const clearNestedExpansionState = () => {
Object.keys(expandedPieceCustomFields).forEach((key) => delete expandedPieceCustomFields[key])
Object.keys(expandedComponentCustomFields).forEach((key) => delete expandedComponentCustomFields[key])
Object.keys(expandedComponentPieces).forEach((key) => delete expandedComponentPieces[key])
Object.keys(expandedComponentPieceCustomFields).forEach((key) => delete expandedComponentPieceCustomFields[key])
}
const setAllExpanded = (value) => {
allExpanded.value = value
expandedSections.customFields = value
expandedSections.machinePieces = value
expandedSections.components = value
expandedCustomFields.value = formData.customFields.map(() => value)
expandedMachinePieces.value = formData.machinePieces.map(() => value)
expandedComponents.value = formData.components.map(() => value)
clearNestedExpansionState()
formData.machinePieces.forEach((piece, pieceIndex) => {
expandedPieceCustomFields[pieceIndex] = (piece.customFields || []).map(() => value)
})
formData.components.forEach((component, componentIndex) => {
expandedComponentCustomFields[componentIndex] = (component.customFields || []).map(() => value)
expandedComponentPieces[componentIndex] = (component.pieces || []).map(() => value)
expandedComponentPieceCustomFields[componentIndex] = {}
component.pieces?.forEach((piece, pieceIndex) => {
expandedComponentPieceCustomFields[componentIndex][pieceIndex] = (piece.customFields || []).map(() => value)
})
})
}
const toggleAllSections = () => {
setAllExpanded(!allExpanded.value)
}
const initializeExpansionState = () => {
setAllExpanded(false)
}
onMounted(() => {
initializeExpansionState()
})
// Méthodes pour les champs personnalisés // Méthodes pour les champs personnalisés
const addCustomField = () => { const addCustomField = () => {
formData.customFields.push({ formData.customFields.push({
@@ -790,10 +1157,12 @@ const addCustomField = () => {
defaultValue: '', defaultValue: '',
optionsText: '' optionsText: ''
}) })
expandedCustomFields.value.push(true)
} }
const removeCustomField = (index) => { const removeCustomField = (index) => {
formData.customFields.splice(index, 1) formData.customFields.splice(index, 1)
expandedCustomFields.value.splice(index, 1)
} }
const updateFieldOptions = (fieldIndex) => { const updateFieldOptions = (fieldIndex) => {
@@ -812,10 +1181,26 @@ const addMachinePiece = () => {
prix: null, prix: null,
customFields: [] customFields: []
}) })
expandedMachinePieces.value.push(true)
const newIndex = formData.machinePieces.length - 1
expandedPieceCustomFields[newIndex] = []
} }
const removeMachinePiece = (index) => { const removeMachinePiece = (index) => {
formData.machinePieces.splice(index, 1) formData.machinePieces.splice(index, 1)
expandedMachinePieces.value.splice(index, 1)
delete expandedPieceCustomFields[index]
// Re-index the remaining nested states
const reorderedPieceStates = {}
Object.keys(expandedPieceCustomFields)
.map(key => Number(key))
.sort((a, b) => a - b)
.forEach((key, position) => {
reorderedPieceStates[position] = expandedPieceCustomFields[key]
})
Object.keys(expandedPieceCustomFields).forEach(key => delete expandedPieceCustomFields[key])
Object.assign(expandedPieceCustomFields, reorderedPieceStates)
} }
const addPieceCustomField = (pieceIndex) => { const addPieceCustomField = (pieceIndex) => {
@@ -829,12 +1214,16 @@ const addPieceCustomField = (pieceIndex) => {
defaultValue: '', defaultValue: '',
optionsText: '' optionsText: ''
}) })
ensurePieceFieldState(pieceIndex)
expandedPieceCustomFields[pieceIndex].push(true)
} }
const removePieceCustomField = (pieceIndex, fieldIndex) => { const removePieceCustomField = (pieceIndex, fieldIndex) => {
if (formData.machinePieces[pieceIndex].customFields) { if (formData.machinePieces[pieceIndex].customFields) {
formData.machinePieces[pieceIndex].customFields.splice(fieldIndex, 1) formData.machinePieces[pieceIndex].customFields.splice(fieldIndex, 1)
} }
ensurePieceFieldState(pieceIndex)
expandedPieceCustomFields[pieceIndex].splice(fieldIndex, 1)
} }
const updatePieceFieldOptions = (pieceIndex, fieldIndex) => { const updatePieceFieldOptions = (pieceIndex, fieldIndex) => {
@@ -854,10 +1243,35 @@ const addComponent = () => {
customFields: [], customFields: [],
pieces: [] pieces: []
}) })
expandedComponents.value.push(true)
const newIndex = formData.components.length - 1
expandedComponentCustomFields[newIndex] = []
expandedComponentPieces[newIndex] = []
expandedComponentPieceCustomFields[newIndex] = {}
} }
const removeComponent = (index) => { const removeComponent = (index) => {
formData.components.splice(index, 1) formData.components.splice(index, 1)
expandedComponents.value.splice(index, 1)
delete expandedComponentCustomFields[index]
delete expandedComponentPieces[index]
delete expandedComponentPieceCustomFields[index]
const reorder = (source) => {
const reordered = {}
Object.keys(source)
.map(key => Number(key))
.sort((a, b) => a - b)
.forEach((key, position) => {
reordered[position] = source[key]
})
Object.keys(source).forEach(key => delete source[key])
Object.assign(source, reordered)
}
reorder(expandedComponentCustomFields)
reorder(expandedComponentPieces)
reorder(expandedComponentPieceCustomFields)
} }
const addComponentCustomField = (componentIndex) => { const addComponentCustomField = (componentIndex) => {
@@ -871,12 +1285,16 @@ const addComponentCustomField = (componentIndex) => {
defaultValue: '', defaultValue: '',
optionsText: '' optionsText: ''
}) })
ensureComponentState(componentIndex)
expandedComponentCustomFields[componentIndex].push(true)
} }
const removeComponentCustomField = (componentIndex, fieldIndex) => { const removeComponentCustomField = (componentIndex, fieldIndex) => {
if (formData.components[componentIndex].customFields) { if (formData.components[componentIndex].customFields) {
formData.components[componentIndex].customFields.splice(fieldIndex, 1) formData.components[componentIndex].customFields.splice(fieldIndex, 1)
} }
ensureComponentState(componentIndex)
expandedComponentCustomFields[componentIndex].splice(fieldIndex, 1)
} }
const updateComponentFieldOptions = (componentIndex, fieldIndex) => { const updateComponentFieldOptions = (componentIndex, fieldIndex) => {
@@ -897,12 +1315,32 @@ const addComponentPiece = (componentIndex) => {
prix: null, prix: null,
customFields: [] customFields: []
}) })
ensureComponentState(componentIndex)
expandedComponentPieces[componentIndex].push(true)
const newPieceIndex = formData.components[componentIndex].pieces.length - 1
ensureComponentPieceState(componentIndex, newPieceIndex)
} }
const removeComponentPiece = (componentIndex, pieceIndex) => { const removeComponentPiece = (componentIndex, pieceIndex) => {
if (formData.components[componentIndex].pieces) { if (formData.components[componentIndex].pieces) {
formData.components[componentIndex].pieces.splice(pieceIndex, 1) formData.components[componentIndex].pieces.splice(pieceIndex, 1)
} }
ensureComponentState(componentIndex)
expandedComponentPieces[componentIndex].splice(pieceIndex, 1)
if (expandedComponentPieceCustomFields[componentIndex]) {
delete expandedComponentPieceCustomFields[componentIndex][pieceIndex]
const reorderedPieceStates = {}
Object.keys(expandedComponentPieceCustomFields[componentIndex])
.map(key => Number(key))
.sort((a, b) => a - b)
.forEach((key, position) => {
reorderedPieceStates[position] = expandedComponentPieceCustomFields[componentIndex][key]
})
Object.keys(expandedComponentPieceCustomFields[componentIndex]).forEach(key => delete expandedComponentPieceCustomFields[componentIndex][key])
Object.assign(expandedComponentPieceCustomFields[componentIndex], reorderedPieceStates)
}
} }
const addPieceCustomFieldToComponent = (componentIndex, pieceIndex) => { const addPieceCustomFieldToComponent = (componentIndex, pieceIndex) => {
@@ -916,12 +1354,16 @@ const addPieceCustomFieldToComponent = (componentIndex, pieceIndex) => {
defaultValue: '', defaultValue: '',
optionsText: '' optionsText: ''
}) })
ensureComponentPieceState(componentIndex, pieceIndex)
expandedComponentPieceCustomFields[componentIndex][pieceIndex].push(true)
} }
const removePieceCustomFieldFromComponent = (componentIndex, pieceIndex, fieldIndex) => { const removePieceCustomFieldFromComponent = (componentIndex, pieceIndex, fieldIndex) => {
if (formData.components[componentIndex]?.pieces?.[pieceIndex]?.customFields) { if (formData.components[componentIndex]?.pieces?.[pieceIndex]?.customFields) {
formData.components[componentIndex].pieces[pieceIndex].customFields.splice(fieldIndex, 1) formData.components[componentIndex].pieces[pieceIndex].customFields.splice(fieldIndex, 1)
} }
ensureComponentPieceState(componentIndex, pieceIndex)
expandedComponentPieceCustomFields[componentIndex][pieceIndex].splice(fieldIndex, 1)
} }
const updateComponentPieceFieldOptions = (componentIndex, pieceIndex, fieldIndex) => { const updateComponentPieceFieldOptions = (componentIndex, pieceIndex, fieldIndex) => {
@@ -941,5 +1383,6 @@ const resetForm = () => {
machinePieces: props.modelValue?.machinePieces || [], machinePieces: props.modelValue?.machinePieces || [],
components: props.modelValue?.components || [] components: props.modelValue?.components || []
}) })
initializeExpansionState()
} }
</script> </script>

View File

@@ -2,38 +2,58 @@
<div class="border border-gray-300 rounded-lg p-4 bg-base-100"> <div class="border border-gray-300 rounded-lg p-4 bg-base-100">
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleDetails"
title="Plier / déplier la pièce"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': expanded }"
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"></path>
</svg>
</button>
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 text-red-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> <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> </svg>
<h4 class="font-medium">{{ piece.name }}</h4> <h4 class="font-medium">{{ piece.name }}</h4>
<span v-if="!expanded && piece.reference" class="text-xs text-gray-500">(Ref: {{ piece.reference }})</span>
</div>
<div v-if="!expanded && quickSummary" class="text-xs text-gray-500">
{{ quickSummary }}
</div> </div>
</div> </div>
<!-- Informations de la pièce --> <div v-if="expanded">
<div class="space-y-1 mb-3"> <div class="space-y-1 mb-3">
<div v-if="piece.reference" class="text-sm"> <div v-if="piece.reference" class="text-sm">
<span class="font-medium">Référence:</span> {{ piece.reference }} <span class="font-medium">Référence:</span> {{ piece.reference }}
</div>
<div v-if="piece.prestataire" class="text-sm">
<span class="font-medium">Prestataire:</span> {{ piece.prestataire }}
</div>
<div v-if="piece.emplacement" class="text-sm">
<span class="font-medium">Emplacement:</span> {{ piece.emplacement }}
</div>
<div v-if="piece.prix" class="text-sm">
<span class="font-medium">Prix:</span> {{ piece.prix }}
</div>
</div> </div>
<div v-if="piece.prestataire" class="text-sm">
<span class="font-medium">Prestataire:</span> {{ piece.prestataire }}
</div>
<div v-if="piece.emplacement" class="text-sm">
<span class="font-medium">Emplacement:</span> {{ piece.emplacement }}
</div>
<div v-if="piece.prix" class="text-sm">
<span class="font-medium">Prix:</span> {{ piece.prix }}
</div>
</div>
<!-- Champs personnalisés de la pièce --> <div v-if="piece.customFields && piece.customFields.length > 0">
<div v-if="piece.customFields && piece.customFields.length > 0"> <h5 class="text-sm font-medium text-gray-700 mb-2">Champs personnalisés:</h5>
<h5 class="text-sm font-medium text-gray-700 mb-2">Champs personnalisés:</h5> <div class="grid grid-cols-1 gap-2">
<div class="grid grid-cols-1 gap-2"> <div v-for="field in piece.customFields" :key="field.id || field.name" class="bg-gray-50 rounded p-2">
<div v-for="field in piece.customFields" :key="field.id" class="bg-gray-50 rounded p-2"> <div class="text-sm font-medium">{{ field.name }}</div>
<div class="text-sm font-medium">{{ field.name }}</div> <div class="text-xs text-gray-600">{{ field.type }}</div>
<div class="text-xs text-gray-600">{{ field.type }}</div> <div v-if="field.required" class="text-xs text-red-500">Obligatoire</div>
<div v-if="field.required" class="text-xs text-red-500">Obligatoire</div> <div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div>
<div v-if="field.defaultValue" class="text-xs text-gray-500">Défaut: {{ field.defaultValue }}</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -41,10 +61,43 @@
</template> </template>
<script setup> <script setup>
defineProps({ import { ref, computed, watch } from 'vue'
const props = defineProps({
piece: { piece: {
type: Object, type: Object,
required: true required: true
},
globalExpandState: {
type: Object,
default: null
} }
}) })
</script>
const expanded = ref(true)
const quickSummary = computed(() => {
const infos = []
if (props.piece?.prestataire) {
infos.push(props.piece.prestataire)
}
if (props.piece?.emplacement) {
infos.push(props.piece.emplacement)
}
return infos.join(' • ')
})
watch(() => props.piece, () => {
expanded.value = props.globalExpandState ? props.globalExpandState.expanded : true
}, { deep: true })
watch(() => props.globalExpandState?.id, () => {
if (props.globalExpandState) {
expanded.value = props.globalExpandState.expanded
}
})
const toggleDetails = () => {
expanded.value = !expanded.value
}
</script>

View File

@@ -1,8 +1,55 @@
<template> <template>
<div class="space-y-4"> <div class="space-y-4">
<div class="flex justify-end">
<button
type="button"
class="btn btn-outline btn-sm"
@click="toggleAllPieces"
>
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
v-if="allExpanded"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 12H6"
></path>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v12m6-6H6"
></path>
</svg>
{{ allExpanded ? 'Tout plier' : 'Tout déplier' }}
</button>
</div>
<div v-for="(piece, index) in pieces" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50"> <div v-for="(piece, index) in pieces" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<h5 class="text-sm font-medium">Nouvelle pièce {{ index + 1 }}</h5> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="togglePieceDetails(index)"
title="Plier / déplier la pièce"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': isPieceExpanded(index) }"
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"></path>
</svg>
</button>
<h5 class="text-sm font-medium">Nouvelle pièce {{ index + 1 }}</h5>
<span v-if="!isPieceExpanded(index)" class="text-xs text-gray-500 truncate max-w-[160px]">
{{ piece.name || 'Sans nom' }}
</span>
</div>
<button <button
type="button" type="button"
@click="removePiece(index)" @click="removePiece(index)"
@@ -14,7 +61,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div v-if="isPieceExpanded(index)" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control"> <div class="form-control">
<label class="label"> <label class="label">
<span class="label-text">Nom de la pièce</span> <span class="label-text">Nom de la pièce</span>
@@ -63,7 +110,7 @@
</div> </div>
<!-- Champs personnalisés de cette pièce --> <!-- Champs personnalisés de cette pièce -->
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-4"> <div v-if="isPieceExpanded(index) && piece.customFields && piece.customFields.length > 0" class="mt-4">
<h6 class="text-xs font-medium text-gray-600 mb-2">Champs personnalisés de cette pièce :</h6> <h6 class="text-xs font-medium text-gray-600 mb-2">Champs personnalisés de cette pièce :</h6>
<div class="space-y-2"> <div class="space-y-2">
<div <div
@@ -72,7 +119,28 @@
class="border border-gray-200 rounded p-2 bg-white" class="border border-gray-200 rounded p-2 bg-white"
> >
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<span class="text-xs font-medium">Champ {{ fieldIndex + 1 }}</span> <div class="flex items-center gap-2">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="togglePieceCustomFieldDetails(index, fieldIndex)"
title="Plier / déplier le champ"
>
<svg
class="w-3 h-3 transition-transform duration-200"
:class="{ 'rotate-90': isPieceCustomFieldExpanded(index, fieldIndex) }"
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"></path>
</svg>
</button>
<span class="text-xs font-medium">Champ {{ fieldIndex + 1 }}</span>
<span v-if="!isPieceCustomFieldExpanded(index, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[120px]">
{{ field.name || 'Sans nom' }}
</span>
</div>
<button <button
type="button" type="button"
@click="removeCustomField(index, fieldIndex)" @click="removeCustomField(index, fieldIndex)"
@@ -84,7 +152,7 @@
</button> </button>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2"> <div v-if="isPieceCustomFieldExpanded(index, fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-2">
<div> <div>
<input <input
v-model="field.name" v-model="field.name"
@@ -105,7 +173,7 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2"> <div v-if="isPieceCustomFieldExpanded(index, fieldIndex)" class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<input <input
v-model="field.required" v-model="field.required"
@@ -125,7 +193,7 @@
</div> </div>
<!-- Options pour les champs de type SELECT --> <!-- Options pour les champs de type SELECT -->
<div v-if="field.type === 'select'" class="mt-2"> <div v-if="isPieceCustomFieldExpanded(index, fieldIndex) && field.type === 'select'" class="mt-2">
<textarea <textarea
v-model="field.optionsText" v-model="field.optionsText"
placeholder="Option 1&#10;Option 2&#10;Option 3" placeholder="Option 1&#10;Option 2&#10;Option 3"
@@ -138,7 +206,7 @@
</div> </div>
<!-- Bouton pour ajouter des champs personnalisés --> <!-- Bouton pour ajouter des champs personnalisés -->
<div class="mt-3"> <div v-if="isPieceExpanded(index)" class="mt-3">
<button <button
type="button" type="button"
@click="addCustomField(index)" @click="addCustomField(index)"
@@ -166,7 +234,7 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, reactive, onMounted, watch } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
@@ -179,6 +247,80 @@ const emit = defineEmits(['update:modelValue'])
const pieces = ref(props.modelValue) const pieces = ref(props.modelValue)
watch(() => props.modelValue, (newValue) => {
pieces.value = newValue
initializeExpansionState()
})
const allExpanded = ref(false)
const expandedPieces = ref([])
const expandedPieceCustomFields = reactive({})
const ensurePieceFieldState = (pieceIndex) => {
if (!expandedPieceCustomFields[pieceIndex]) {
expandedPieceCustomFields[pieceIndex] = []
}
}
const isPieceExpanded = (index) => {
if (expandedPieces.value[index] === undefined) {
expandedPieces.value[index] = allExpanded.value
}
return expandedPieces.value[index]
}
const togglePieceDetails = (index) => {
expandedPieces.value[index] = !isPieceExpanded(index)
if (!expandedPieces.value[index]) {
allExpanded.value = false
}
}
const isPieceCustomFieldExpanded = (pieceIndex, fieldIndex) => {
ensurePieceFieldState(pieceIndex)
if (expandedPieceCustomFields[pieceIndex][fieldIndex] === undefined) {
expandedPieceCustomFields[pieceIndex][fieldIndex] = allExpanded.value
}
return expandedPieceCustomFields[pieceIndex][fieldIndex]
}
const togglePieceCustomFieldDetails = (pieceIndex, fieldIndex) => {
ensurePieceFieldState(pieceIndex)
expandedPieceCustomFields[pieceIndex][fieldIndex] = !isPieceCustomFieldExpanded(pieceIndex, fieldIndex)
if (!expandedPieceCustomFields[pieceIndex][fieldIndex]) {
allExpanded.value = false
}
}
const clearExpansionState = () => {
expandedPieces.value = []
Object.keys(expandedPieceCustomFields).forEach((key) => delete expandedPieceCustomFields[key])
}
const setAllExpanded = (value) => {
allExpanded.value = value
expandedPieces.value = pieces.value.map(() => value)
Object.keys(expandedPieceCustomFields).forEach((key) => delete expandedPieceCustomFields[key])
pieces.value.forEach((piece, index) => {
expandedPieceCustomFields[index] = (piece.customFields || []).map(() => value)
})
}
const toggleAllPieces = () => {
setAllExpanded(!allExpanded.value)
}
const initializeExpansionState = () => {
clearExpansionState()
setAllExpanded(false)
}
onMounted(() => {
initializeExpansionState()
})
// Méthodes pour les pièces // Méthodes pour les pièces
const addPiece = () => { const addPiece = () => {
pieces.value.push({ pieces.value.push({
@@ -190,11 +332,26 @@ const addPiece = () => {
customFields: [] customFields: []
}) })
emit('update:modelValue', pieces.value) emit('update:modelValue', pieces.value)
expandedPieces.value.push(allExpanded.value)
ensurePieceFieldState(pieces.value.length - 1)
} }
const removePiece = (index) => { const removePiece = (index) => {
pieces.value.splice(index, 1) pieces.value.splice(index, 1)
emit('update:modelValue', pieces.value) emit('update:modelValue', pieces.value)
expandedPieces.value.splice(index, 1)
delete expandedPieceCustomFields[index]
const reordered = {}
Object.keys(expandedPieceCustomFields)
.map(key => Number(key))
.sort((a, b) => a - b)
.forEach((key, position) => {
reordered[position] = expandedPieceCustomFields[key]
})
Object.keys(expandedPieceCustomFields).forEach(key => delete expandedPieceCustomFields[key])
Object.assign(expandedPieceCustomFields, reordered)
} }
const addCustomField = (pieceIndex) => { const addCustomField = (pieceIndex) => {
@@ -208,12 +365,16 @@ const addCustomField = (pieceIndex) => {
defaultValue: '', defaultValue: '',
optionsText: '' optionsText: ''
}) })
ensurePieceFieldState(pieceIndex)
expandedPieceCustomFields[pieceIndex].push(allExpanded.value)
} }
const removeCustomField = (pieceIndex, fieldIndex) => { const removeCustomField = (pieceIndex, fieldIndex) => {
if (pieces.value[pieceIndex].customFields) { if (pieces.value[pieceIndex].customFields) {
pieces.value[pieceIndex].customFields.splice(fieldIndex, 1) pieces.value[pieceIndex].customFields.splice(fieldIndex, 1)
} }
ensurePieceFieldState(pieceIndex)
expandedPieceCustomFields[pieceIndex].splice(fieldIndex, 1)
} }
const updateFieldOptions = (pieceIndex, fieldIndex) => { const updateFieldOptions = (pieceIndex, fieldIndex) => {
@@ -221,4 +382,4 @@ const updateFieldOptions = (pieceIndex, fieldIndex) => {
pieces.value[pieceIndex].customFields[fieldIndex].optionsText = pieces.value[pieceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n') pieces.value[pieceIndex].customFields[fieldIndex].optionsText = pieces.value[pieceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
} }
} }
</script> </script>

View File

@@ -24,6 +24,36 @@
</h2> </h2>
<form @submit.prevent="generateType" class="space-y-6"> <form @submit.prevent="generateType" class="space-y-6">
<div class="flex justify-end">
<button
type="button"
class="btn btn-outline btn-sm"
@click="toggleAllSections"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
v-if="allSectionsExpanded"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 12H6"
></path>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v12m6-6H6"
></path>
</svg>
{{ allSectionsExpanded ? 'Tout plier' : 'Tout déplier' }}
</button>
</div>
<!-- Basic Information --> <!-- Basic Information -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="form-control"> <div class="form-control">
@@ -81,11 +111,31 @@
<!-- Machine Pieces (Root Level) --> <!-- Machine Pieces (Root Level) -->
<div class="form-control"> <div class="form-control">
<label class="label"> <div class="flex items-center justify-between">
<span class="label-text">Pièces de la machine</span> <div class="flex items-center gap-2">
<span class="label-text-alt">Pièces directement attachées à la machine (optionnel)</span> <button
</label> type="button"
<div class="space-y-4"> class="btn btn-ghost btn-sm p-1"
@click="toggleSection('machinePieces')"
title="Plier / déplier les pièces"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': sections.machinePieces }"
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"></path>
</svg>
</button>
<label class="label m-0 flex flex-col items-start">
<span class="label-text">Pièces de la machine</span>
<span class="label-text-alt">Pièces directement attachées à la machine (optionnel)</span>
</label>
</div>
</div>
<div v-if="sections.machinePieces" class="space-y-4">
<div v-for="(piece, index) in newType.machinePieces" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50"> <div v-for="(piece, index) in newType.machinePieces" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<h5 class="text-sm font-medium">Pièce {{ index + 1 }}</h5> <h5 class="text-sm font-medium">Pièce {{ index + 1 }}</h5>
@@ -221,12 +271,32 @@
<!-- Hierarchical Components --> <!-- Hierarchical Components -->
<div class="form-control"> <div class="form-control">
<label class="label"> <div class="flex items-center justify-between">
<span class="label-text">Structure hiérarchique</span> <div class="flex items-center gap-2">
<span class="label-text-alt">Définissez les composants, sous-composants et leurs pièces (optionnel)</span> <button
</label> type="button"
class="btn btn-ghost btn-sm p-1"
@click="toggleSection('components')"
title="Plier / déplier la structure"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': sections.components }"
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"></path>
</svg>
</button>
<label class="label m-0 flex flex-col items-start">
<span class="label-text">Structure hiérarchique</span>
<span class="label-text-alt">Définissez les composants, sous-composants et leurs pièces (optionnel)</span>
</label>
</div>
</div>
<div class="space-y-4"> <div v-if="sections.components" class="space-y-4">
<!-- Root Components --> <!-- Root Components -->
<div class="space-y-3"> <div class="space-y-3">
<h4 class="text-sm font-semibold text-gray-700">Composants principaux :</h4> <h4 class="text-sm font-semibold text-gray-700">Composants principaux :</h4>
@@ -236,6 +306,22 @@
class="border border-gray-200 rounded-lg p-4 bg-gray-50" class="border border-gray-200 rounded-lg p-4 bg-gray-50"
> >
<div class="flex items-center gap-2 mb-3"> <div class="flex items-center gap-2 mb-3">
<button
type="button"
class="btn btn-ghost btn-xs p-1"
@click="toggleComponentDetails(index)"
title="Plier / déplier le composant"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': isComponentExpanded(index) }"
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"></path>
</svg>
</button>
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
</svg> </svg>
@@ -257,6 +343,8 @@
</button> </button>
</div> </div>
<div v-if="isComponentExpanded(index)">
<!-- Champs personnalisés du composant --> <!-- Champs personnalisés du composant -->
<div class="mb-3"> <div class="mb-3">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
@@ -882,7 +970,8 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<button <button
type="button" type="button"
@click="addComponent" @click="addComponent"
@@ -899,11 +988,31 @@
<!-- Custom Fields --> <!-- Custom Fields -->
<div class="form-control"> <div class="form-control">
<label class="label"> <div class="flex items-center justify-between">
<span class="label-text">Champs personnalisés</span> <div class="flex items-center gap-2">
<span class="label-text-alt">Définissez des champs personnalisés pour ce type de machine (optionnel)</span> <button
</label> type="button"
<div class="space-y-4"> class="btn btn-ghost btn-sm p-1"
@click="toggleSection('customFields')"
title="Plier / déplier les champs personnalisés"
>
<svg
class="w-4 h-4 transition-transform duration-200"
:class="{ 'rotate-90': sections.customFields }"
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"></path>
</svg>
</button>
<label class="label m-0 flex flex-col items-start">
<span class="label-text">Champs personnalisés</span>
<span class="label-text-alt">Définissez des champs personnalisés pour ce type de machine (optionnel)</span>
</label>
</div>
</div>
<div v-if="sections.customFields" class="space-y-4">
<div v-for="(field, index) in newType.customFields" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50"> <div v-for="(field, index) in newType.customFields" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<h5 class="text-sm font-medium">Champ personnalisé {{ index + 1 }}</h5> <h5 class="text-sm font-medium">Champ personnalisé {{ index + 1 }}</h5>
@@ -1043,7 +1152,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, computed, onMounted } from 'vue'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi' import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useToast } from '~/composables/useToast' import { useToast } from '~/composables/useToast'
@@ -1071,6 +1180,33 @@ const newType = reactive({
customFields: [] // Changed to empty array to make it optional customFields: [] // Changed to empty array to make it optional
}) })
const sections = reactive({
machinePieces: true,
components: true,
customFields: true
})
const expandedComponents = reactive([])
const allSectionsExpanded = computed(() => sections.machinePieces && sections.components && sections.customFields)
const toggleSection = (section) => {
sections[section] = !sections[section]
}
const toggleAllSections = () => {
const next = !allSectionsExpanded.value
sections.machinePieces = next
sections.components = next
sections.customFields = next
expandedComponents.splice(0, expandedComponents.length, ...newType.components.map(() => next))
}
const isComponentExpanded = (index) => expandedComponents[index] ?? true
const toggleComponentDetails = (index) => {
expandedComponents[index] = !isComponentExpanded(index)
}
// Methods for hierarchical components // Methods for hierarchical components
const addComponent = () => { const addComponent = () => {
newType.components.push({ newType.components.push({
@@ -1086,10 +1222,12 @@ const addComponent = () => {
pieces: [{ name: '', customFields: [] }], pieces: [{ name: '', customFields: [] }],
customFields: [] customFields: []
}) })
expandedComponents.push(true)
} }
const removeComponent = (index) => { const removeComponent = (index) => {
newType.components.splice(index, 1) newType.components.splice(index, 1)
expandedComponents.splice(index, 1)
} }
const addSubComponent = (component) => { const addSubComponent = (component) => {
@@ -1339,6 +1477,7 @@ const resetForm = () => {
newType.components = [] // Reset to empty array newType.components = [] // Reset to empty array
newType.machinePieces = [] // Reset to empty array newType.machinePieces = [] // Reset to empty array
newType.customFields = [] // Reset to empty array newType.customFields = [] // Reset to empty array
expandedComponents.splice(0, expandedComponents.length)
} }
const generateType = async () => { const generateType = async () => {
@@ -1413,4 +1552,4 @@ onMounted(async () => {
await loadMachineTypes() await loadMachineTypes()
recentTypes.value = machineTypes.value.slice(-3).reverse() recentTypes.value = machineTypes.value.slice(-3).reverse()
}) })
</script> </script>

View File

@@ -18,7 +18,7 @@
<p class="mt-4 text-gray-600">Chargement du type...</p> <p class="mt-4 text-gray-600">Chargement du type...</p>
</div> </div>
<!-- Edit Form --> <!-- Type Details -->
<div v-else-if="type" class="my-8"> <div v-else-if="type" class="my-8">
<div class="card bg-base-100 shadow-xl"> <div class="card bg-base-100 shadow-xl">
<div class="card-body"> <div class="card-body">
@@ -42,6 +42,37 @@
<!-- Current Type Info --> <!-- Current Type Info -->
<TypeInfoDisplay :type="type" /> <TypeInfoDisplay :type="type" />
<div v-if="hasExpandableContent" class="flex justify-end mb-6">
<button
type="button"
class="btn btn-outline btn-sm"
@click="toggleGlobalExpand"
>
<svg
class="w-4 h-4 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
v-if="globalExpandState.expanded"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 12H6"
></path>
<path
v-else
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v12m6-6H6"
></path>
</svg>
{{ globalExpandState.expanded ? 'Tout plier' : 'Tout déplier' }}
</button>
</div>
<!-- Affichage des composants existants --> <!-- Affichage des composants existants -->
<div v-if="type.components && type.components.length > 0" class="mb-8"> <div v-if="type.components && type.components.length > 0" class="mb-8">
<h3 class="text-lg font-semibold mb-4">Composants existants</h3> <h3 class="text-lg font-semibold mb-4">Composants existants</h3>
@@ -50,6 +81,7 @@
v-for="(component, componentIndex) in type.components" v-for="(component, componentIndex) in type.components"
:key="componentIndex" :key="componentIndex"
:component="component" :component="component"
:global-expand-state="globalExpandState"
/> />
</div> </div>
</div> </div>
@@ -62,45 +94,10 @@
v-for="(piece, pieceIndex) in type.machinePieces" v-for="(piece, pieceIndex) in type.machinePieces"
:key="pieceIndex" :key="pieceIndex"
:piece="piece" :piece="piece"
:global-expand-state="globalExpandState"
/> />
</div> </div>
</div> </div>
<form @submit.prevent="saveChanges" class="space-y-6">
<!-- Add Machine Pieces -->
<div class="form-control">
<label class="label">
<span class="label-text">Ajouter des pièces de machine</span>
<span class="label-text-alt">Nouvelles pièces directement attachées à la machine</span>
</label>
<TypeMachinePieceForm v-model="newMachinePieces" />
</div>
<!-- Add Components -->
<div class="form-control">
<label class="label">
<span class="label-text">Ajouter des composants</span>
<span class="label-text-alt">Nouveaux composants principaux avec sous-composants</span>
</label>
<TypeComponentForm v-model="newComponents" />
</div>
<!-- Actions -->
<div class="card-actions justify-end">
<button type="button" @click="resetForm" class="btn btn-outline">
Réinitialiser
</button>
<button type="submit" class="btn btn-primary" :disabled="saving">
<svg v-if="saving" class="w-5 h-5 mr-2 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<svg v-else class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
{{ saving ? 'Sauvegarde...' : 'Sauvegarder les modifications' }}
</button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
@@ -121,127 +118,46 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute } from 'vue-router'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi' import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useToast } from '~/composables/useToast' import { useToast } from '~/composables/useToast'
const route = useRoute() const route = useRoute()
const router = useRouter() const { getMachineTypeById } = useMachineTypesApi()
const { getMachineTypeById, updateMachineType } = useMachineTypesApi() const { showError } = useToast()
const { showSuccess, showError } = useToast()
const type = ref(null) const type = ref(null)
const loading = ref(true) const loading = ref(true)
const saving = ref(false)
// Nouvelles données à ajouter const globalExpandState = reactive({
const newMachinePieces = ref([]) expanded: true,
const newComponents = ref([]) id: 0
})
const resetForm = () => { const hasExpandableContent = computed(() => {
newMachinePieces.value = [] const componentCount = type.value?.components?.length || 0
newComponents.value = [] const pieceCount = type.value?.machinePieces?.length || 0
return componentCount + pieceCount > 0
})
const toggleGlobalExpand = () => {
globalExpandState.expanded = !globalExpandState.expanded
globalExpandState.id += 1
} }
const saveChanges = async () => {
try {
saving.value = true
// Préparer les données mises à jour
const updatedType = {
...type.value,
machinePieces: [
...(type.value.machinePieces || []),
...newMachinePieces.value
.filter(piece => piece.name.trim() !== '')
.map(piece => ({
name: piece.name,
reference: piece.reference,
prestataire: piece.prestataire,
emplacement: piece.emplacement,
prix: piece.prix,
customFields: piece.customFields || []
}))
],
components: [
...(type.value.components || []),
...newComponents.value
.filter(comp => comp.name.trim() !== '')
.map(comp => ({
name: comp.name,
reference: comp.reference,
prestataire: comp.prestataire,
emplacement: comp.emplacement,
prix: comp.prix,
customFields: comp.customFields || [],
pieces: comp.pieces
.filter(piece => piece.name.trim() !== '')
.map(piece => ({
name: piece.name,
reference: piece.reference,
prestataire: piece.prestataire,
emplacement: piece.emplacement,
prix: piece.prix,
customFields: piece.customFields || []
})),
subComponents: comp.subComponents
.filter(subComp => subComp.name.trim() !== '')
.map(subComp => ({
name: subComp.name,
reference: subComp.reference,
prestataire: subComp.prestataire,
emplacement: subComp.emplacement,
prix: subComp.prix,
customFields: subComp.customFields || [],
pieces: subComp.pieces
.filter(piece => piece.name.trim() !== '')
.map(piece => ({
name: piece.name,
reference: piece.reference,
prestataire: piece.prestataire,
emplacement: piece.emplacement,
prix: piece.prix,
customFields: piece.customFields || []
}))
}))
}))
]
}
const result = await updateMachineType(type.value.id, updatedType)
if (result.success) {
showSuccess('Type mis à jour avec succès !')
router.push('/types')
} else {
showError('Erreur lors de la mise à jour du type')
}
} catch (error) {
console.error('Erreur lors de la sauvegarde:', error)
showError('Erreur lors de la sauvegarde')
} finally {
saving.value = false
}
}
// Charger le type au montage
onMounted(async () => { onMounted(async () => {
try { try {
const typeId = route.params.id const typeId = route.params.id
console.log('=== EDIT PAGE LOADING ===') console.log('=== TYPE DETAIL PAGE LOADING ===')
console.log('Loading type with ID:', typeId) console.log('Loading type with ID:', typeId)
console.log('Route params:', route.params)
console.log('Current route:', route.path)
const result = await getMachineTypeById(typeId) const result = await getMachineTypeById(typeId)
console.log('API Result:', result) console.log('API Result:', result)
if (result.success) { if (result.success) {
type.value = result.data type.value = result.data
console.log('Type loaded successfully:', type.value) console.log('Type loaded successfully:', type.value)
console.log('Type ID:', type.value.id)
console.log('Type name:', type.value.name)
} else { } else {
console.error('Failed to load type:', result.error) console.error('Failed to load type:', result.error)
showError('Type non trouvé') showError('Type non trouvé')
@@ -254,4 +170,4 @@ onMounted(async () => {
console.log('Loading finished, loading.value:', loading.value) console.log('Loading finished, loading.value:', loading.value)
} }
}) })
</script> </script>