feat: refactor type management screens
This commit is contained in:
@@ -2,156 +2,521 @@
|
||||
<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 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">
|
||||
<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>
|
||||
<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 class="text-sm text-gray-500">
|
||||
{{ component.reference ? `Ref: ${component.reference}` : '' }}
|
||||
<div v-if="!expanded && compactInfo" class="text-xs text-gray-500">
|
||||
{{ compactInfo }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Informations du composant -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div v-if="component.prestataire" class="text-sm">
|
||||
<span class="font-medium">Prestataire:</span> {{ component.prestataire }}
|
||||
</div>
|
||||
<div v-if="component.emplacement" class="text-sm">
|
||||
<span class="font-medium">Emplacement:</span> {{ component.emplacement }}
|
||||
</div>
|
||||
<div v-if="component.prix" class="text-sm">
|
||||
<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>
|
||||
<template v-if="expanded">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div v-if="component.prestataire" class="text-sm">
|
||||
<span class="font-medium">Prestataire:</span> {{ component.prestataire }}
|
||||
</div>
|
||||
<div v-if="component.emplacement" class="text-sm">
|
||||
<span class="font-medium">Emplacement:</span> {{ component.emplacement }}
|
||||
</div>
|
||||
<div v-if="component.prix" class="text-sm">
|
||||
<span class="font-medium">Prix:</span> {{ component.prix }}€
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pièces du composant -->
|
||||
<div v-if="component.pieces && component.pieces.length > 0" class="mb-4">
|
||||
<h5 class="text-sm font-medium text-gray-700 mb-2">Pièces du composant:</h5>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(piece, pieceIndex) in component.pieces" :key="pieceIndex" 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">
|
||||
<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>
|
||||
<span class="font-medium">{{ piece.name }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ piece.reference ? `Ref: ${piece.reference}` : '' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Informations de la pièce -->
|
||||
<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>
|
||||
|
||||
<!-- 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 v-if="component.customFields && component.customFields.length > 0" class="mb-4">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h5 class="text-sm font-medium text-gray-700">Champs personnalisés</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-xs p-1"
|
||||
@click="toggleSection('customFields')"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 transition-transform duration-200"
|
||||
:class="{ 'rotate-90': expandedSections.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>
|
||||
</div>
|
||||
<div v-if="expandedSections.customFields" 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 || field.name" 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>
|
||||
|
||||
<!-- Sous-composants -->
|
||||
<div v-if="component.subComponents && component.subComponents.length > 0" class="ml-4 border-l-2 border-gray-300 pl-4">
|
||||
<h5 class="text-sm font-medium text-gray-700 mb-2">Sous-composants:</h5>
|
||||
<div 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">
|
||||
<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>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ subComponent.reference ? `Ref: ${subComponent.reference}` : '' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Informations du sous-composant -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mb-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>
|
||||
|
||||
<!-- Champs personnalisés du sous-composant -->
|
||||
<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>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-1">
|
||||
<div v-for="field in subComponent.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>
|
||||
|
||||
<!-- 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">
|
||||
<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">Pièces du composant</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost btn-xs p-1"
|
||||
@click="toggleSection('pieces')"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 transition-transform duration-200"
|
||||
:class="{ 'rotate-90': expandedSections.pieces }"
|
||||
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.pieces" class="space-y-2">
|
||||
<div
|
||||
v-for="(piece, pieceIndex) in component.pieces"
|
||||
:key="pieceIndex"
|
||||
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="togglePieceDetails(pieceIndex)"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 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">
|
||||
<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">{{ piece.name }}</span>
|
||||
<span v-if="piece.reference" class="text-xs text-gray-500">({{ piece.reference }})</span>
|
||||
<span v-if="piece.prix" class="text-xs text-green-600">{{ piece.prix }}€</span>
|
||||
<span class="font-medium">{{ piece.name }}</span>
|
||||
<span v-if="!isPieceExpanded(pieceIndex) && piece.reference" class="text-xs text-gray-500">(Ref: {{ piece.reference }})</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 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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
component: {
|
||||
type: Object,
|
||||
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
@@ -1,5 +1,36 @@
|
||||
<template>
|
||||
<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 -->
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
@@ -101,10 +132,29 @@
|
||||
>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
@@ -118,7 +168,7 @@
|
||||
</button>
|
||||
</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">
|
||||
<label class="label">
|
||||
<span class="label-text">Nom du champ</span>
|
||||
@@ -149,7 +199,7 @@
|
||||
</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">
|
||||
<input
|
||||
v-model="field.required"
|
||||
@@ -173,7 +223,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<span class="label-text">Options de la liste</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 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">
|
||||
<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>
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
@@ -250,7 +319,7 @@
|
||||
</button>
|
||||
</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">
|
||||
<label class="label">
|
||||
<span class="label-text">Nom de la pièce</span>
|
||||
@@ -303,7 +372,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<span class="text-sm font-medium">Champs personnalisés de cette pièce :</span>
|
||||
<button
|
||||
@@ -325,7 +394,28 @@
|
||||
class="border border-gray-200 rounded p-2 bg-white"
|
||||
>
|
||||
<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
|
||||
type="button"
|
||||
@click="removePieceCustomField(pieceIndex, fieldIndex)"
|
||||
@@ -337,7 +427,7 @@
|
||||
</button>
|
||||
</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
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
@@ -354,7 +444,7 @@
|
||||
</select>
|
||||
</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">
|
||||
<input
|
||||
v-model="field.required"
|
||||
@@ -372,7 +462,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
@@ -431,10 +521,29 @@
|
||||
>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
@@ -448,7 +557,7 @@
|
||||
</button>
|
||||
</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">
|
||||
<label class="label">
|
||||
<span class="label-text">Nom du composant</span>
|
||||
@@ -501,7 +610,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<span class="text-sm font-medium">Champs personnalisés du composant :</span>
|
||||
<button
|
||||
@@ -523,7 +632,28 @@
|
||||
class="border border-gray-200 rounded p-2 bg-white"
|
||||
>
|
||||
<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
|
||||
type="button"
|
||||
@click="removeComponentCustomField(componentIndex, fieldIndex)"
|
||||
@@ -535,7 +665,7 @@
|
||||
</button>
|
||||
</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
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
@@ -552,7 +682,7 @@
|
||||
</select>
|
||||
</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">
|
||||
<input
|
||||
v-model="field.required"
|
||||
@@ -570,7 +700,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
@@ -606,15 +736,40 @@
|
||||
>
|
||||
<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="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">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
<input
|
||||
v-model="piece.name"
|
||||
type="text"
|
||||
:placeholder="`Pièce ${pieceIndex + 1}`"
|
||||
class="input input-bordered input-xs flex-1"
|
||||
/>
|
||||
<span class="text-xs font-medium">Pièce {{ pieceIndex + 1 }}</span>
|
||||
<template v-if="isComponentPieceExpanded(componentIndex, pieceIndex)">
|
||||
<input
|
||||
v-model="piece.name"
|
||||
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 class="flex gap-1">
|
||||
<button
|
||||
@@ -641,7 +796,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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
|
||||
v-for="(field, fieldIndex) in piece.customFields"
|
||||
@@ -649,7 +804,28 @@
|
||||
class="border border-gray-100 rounded p-1 bg-gray-50"
|
||||
>
|
||||
<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
|
||||
type="button"
|
||||
@click="removePieceCustomFieldFromComponent(componentIndex, pieceIndex, fieldIndex)"
|
||||
@@ -661,7 +837,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<div v-if="isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex)" class="grid grid-cols-2 gap-1">
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
@@ -678,7 +854,7 @@
|
||||
</select>
|
||||
</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">
|
||||
<input
|
||||
v-model="field.required"
|
||||
@@ -696,7 +872,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
@@ -738,7 +914,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -755,11 +931,21 @@ const emit = defineEmits(['update:modelValue', 'submit'])
|
||||
|
||||
// État des sections dépliées/repliées
|
||||
const expandedSections = reactive({
|
||||
customFields: true,
|
||||
machinePieces: true,
|
||||
components: true
|
||||
customFields: false,
|
||||
machinePieces: false,
|
||||
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
|
||||
const formData = reactive({
|
||||
name: props.modelValue?.name || '',
|
||||
@@ -779,8 +965,189 @@ watch(formData, (newValue) => {
|
||||
// Méthode pour basculer l'état d'une section
|
||||
const toggleSection = (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
|
||||
const addCustomField = () => {
|
||||
formData.customFields.push({
|
||||
@@ -790,10 +1157,12 @@ const addCustomField = () => {
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
expandedCustomFields.value.push(true)
|
||||
}
|
||||
|
||||
const removeCustomField = (index) => {
|
||||
formData.customFields.splice(index, 1)
|
||||
expandedCustomFields.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const updateFieldOptions = (fieldIndex) => {
|
||||
@@ -812,10 +1181,26 @@ const addMachinePiece = () => {
|
||||
prix: null,
|
||||
customFields: []
|
||||
})
|
||||
expandedMachinePieces.value.push(true)
|
||||
const newIndex = formData.machinePieces.length - 1
|
||||
expandedPieceCustomFields[newIndex] = []
|
||||
}
|
||||
|
||||
const removeMachinePiece = (index) => {
|
||||
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) => {
|
||||
@@ -829,12 +1214,16 @@ const addPieceCustomField = (pieceIndex) => {
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
ensurePieceFieldState(pieceIndex)
|
||||
expandedPieceCustomFields[pieceIndex].push(true)
|
||||
}
|
||||
|
||||
const removePieceCustomField = (pieceIndex, fieldIndex) => {
|
||||
if (formData.machinePieces[pieceIndex].customFields) {
|
||||
formData.machinePieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
ensurePieceFieldState(pieceIndex)
|
||||
expandedPieceCustomFields[pieceIndex].splice(fieldIndex, 1)
|
||||
}
|
||||
|
||||
const updatePieceFieldOptions = (pieceIndex, fieldIndex) => {
|
||||
@@ -854,10 +1243,35 @@ const addComponent = () => {
|
||||
customFields: [],
|
||||
pieces: []
|
||||
})
|
||||
expandedComponents.value.push(true)
|
||||
const newIndex = formData.components.length - 1
|
||||
expandedComponentCustomFields[newIndex] = []
|
||||
expandedComponentPieces[newIndex] = []
|
||||
expandedComponentPieceCustomFields[newIndex] = {}
|
||||
}
|
||||
|
||||
const removeComponent = (index) => {
|
||||
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) => {
|
||||
@@ -871,12 +1285,16 @@ const addComponentCustomField = (componentIndex) => {
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
ensureComponentState(componentIndex)
|
||||
expandedComponentCustomFields[componentIndex].push(true)
|
||||
}
|
||||
|
||||
const removeComponentCustomField = (componentIndex, fieldIndex) => {
|
||||
if (formData.components[componentIndex].customFields) {
|
||||
formData.components[componentIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
ensureComponentState(componentIndex)
|
||||
expandedComponentCustomFields[componentIndex].splice(fieldIndex, 1)
|
||||
}
|
||||
|
||||
const updateComponentFieldOptions = (componentIndex, fieldIndex) => {
|
||||
@@ -897,12 +1315,32 @@ const addComponentPiece = (componentIndex) => {
|
||||
prix: null,
|
||||
customFields: []
|
||||
})
|
||||
ensureComponentState(componentIndex)
|
||||
expandedComponentPieces[componentIndex].push(true)
|
||||
const newPieceIndex = formData.components[componentIndex].pieces.length - 1
|
||||
ensureComponentPieceState(componentIndex, newPieceIndex)
|
||||
}
|
||||
|
||||
const removeComponentPiece = (componentIndex, pieceIndex) => {
|
||||
if (formData.components[componentIndex].pieces) {
|
||||
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) => {
|
||||
@@ -916,12 +1354,16 @@ const addPieceCustomFieldToComponent = (componentIndex, pieceIndex) => {
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
ensureComponentPieceState(componentIndex, pieceIndex)
|
||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex].push(true)
|
||||
}
|
||||
|
||||
const removePieceCustomFieldFromComponent = (componentIndex, pieceIndex, fieldIndex) => {
|
||||
if (formData.components[componentIndex]?.pieces?.[pieceIndex]?.customFields) {
|
||||
formData.components[componentIndex].pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
ensureComponentPieceState(componentIndex, pieceIndex)
|
||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex].splice(fieldIndex, 1)
|
||||
}
|
||||
|
||||
const updateComponentPieceFieldOptions = (componentIndex, pieceIndex, fieldIndex) => {
|
||||
@@ -941,5 +1383,6 @@ const resetForm = () => {
|
||||
machinePieces: props.modelValue?.machinePieces || [],
|
||||
components: props.modelValue?.components || []
|
||||
})
|
||||
initializeExpansionState()
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -2,38 +2,58 @@
|
||||
<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 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">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<!-- Informations de la pièce -->
|
||||
<div class="space-y-1 mb-3">
|
||||
<div v-if="piece.reference" class="text-sm">
|
||||
<span class="font-medium">Référence:</span> {{ piece.reference }}
|
||||
<div v-if="expanded">
|
||||
<div class="space-y-1 mb-3">
|
||||
<div v-if="piece.reference" class="text-sm">
|
||||
<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 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">
|
||||
<h5 class="text-sm font-medium text-gray-700 mb-2">Champs personnalisés:</h5>
|
||||
<div class="grid grid-cols-1 gap-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-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 v-if="piece.customFields && piece.customFields.length > 0">
|
||||
<h5 class="text-sm font-medium text-gray-700 mb-2">Champs personnalisés:</h5>
|
||||
<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 class="text-sm 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>
|
||||
@@ -41,10 +61,43 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
import { ref, computed, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
piece: {
|
||||
type: Object,
|
||||
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>
|
||||
|
||||
@@ -1,8 +1,55 @@
|
||||
<template>
|
||||
<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 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
|
||||
type="button"
|
||||
@click="removePiece(index)"
|
||||
@@ -14,7 +61,7 @@
|
||||
</button>
|
||||
</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">
|
||||
<label class="label">
|
||||
<span class="label-text">Nom de la pièce</span>
|
||||
@@ -63,7 +110,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
@@ -72,7 +119,28 @@
|
||||
class="border border-gray-200 rounded p-2 bg-white"
|
||||
>
|
||||
<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
|
||||
type="button"
|
||||
@click="removeCustomField(index, fieldIndex)"
|
||||
@@ -84,7 +152,7 @@
|
||||
</button>
|
||||
</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>
|
||||
<input
|
||||
v-model="field.name"
|
||||
@@ -105,7 +173,7 @@
|
||||
</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">
|
||||
<input
|
||||
v-model="field.required"
|
||||
@@ -125,7 +193,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
@@ -138,7 +206,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Bouton pour ajouter des champs personnalisés -->
|
||||
<div class="mt-3">
|
||||
<div v-if="isPieceExpanded(index)" class="mt-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="addCustomField(index)"
|
||||
@@ -166,7 +234,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
@@ -179,6 +247,80 @@ const emit = defineEmits(['update: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
|
||||
const addPiece = () => {
|
||||
pieces.value.push({
|
||||
@@ -190,11 +332,26 @@ const addPiece = () => {
|
||||
customFields: []
|
||||
})
|
||||
emit('update:modelValue', pieces.value)
|
||||
expandedPieces.value.push(allExpanded.value)
|
||||
ensurePieceFieldState(pieces.value.length - 1)
|
||||
}
|
||||
|
||||
const removePiece = (index) => {
|
||||
pieces.value.splice(index, 1)
|
||||
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) => {
|
||||
@@ -208,12 +365,16 @@ const addCustomField = (pieceIndex) => {
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
ensurePieceFieldState(pieceIndex)
|
||||
expandedPieceCustomFields[pieceIndex].push(allExpanded.value)
|
||||
}
|
||||
|
||||
const removeCustomField = (pieceIndex, fieldIndex) => {
|
||||
if (pieces.value[pieceIndex].customFields) {
|
||||
pieces.value[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
ensurePieceFieldState(pieceIndex)
|
||||
expandedPieceCustomFields[pieceIndex].splice(fieldIndex, 1)
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user