feat: Composants de formulaire pour les types de machines
- TypeComponentForm.vue : Formulaire pour ajouter des composants avec hiérarchie - TypeMachinePieceForm.vue : Formulaire pour ajouter des pièces principales - Support des champs personnalisés avec différents types (text, number, select, boolean, date) - Gestion des sous-composants et pièces imbriquées - Validation et gestion des options pour les champs de type SELECT
This commit is contained in:
698
app/components/TypeComponentForm.vue
Normal file
698
app/components/TypeComponentForm.vue
Normal file
@@ -0,0 +1,698 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div v-for="(component, index) in components" :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">Nouveau composant {{ index + 1 }}</h5>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeComponent(index)"
|
||||
class="btn btn-square btn-error btn-sm"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Nom du composant</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="component.name"
|
||||
type="text"
|
||||
placeholder="Nom du composant"
|
||||
class="input input-bordered input-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Référence</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="component.reference"
|
||||
type="text"
|
||||
placeholder="Référence"
|
||||
class="input input-bordered input-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Prestataire</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="component.prestataire"
|
||||
type="text"
|
||||
placeholder="Prestataire"
|
||||
class="input input-bordered input-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Emplacement</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="component.emplacement"
|
||||
type="text"
|
||||
placeholder="Emplacement"
|
||||
class="input input-bordered input-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Champs personnalisés du composant -->
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-xs font-medium text-gray-600">Champs personnalisés du composant :</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="addComponentCustomField(component)"
|
||||
class="btn btn-xs btn-outline"
|
||||
>
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
Ajouter champ
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="(field, fieldIndex) in component.customFields"
|
||||
:key="fieldIndex"
|
||||
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 personnalisé {{ fieldIndex + 1 }}</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeComponentCustomField(component, fieldIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<div>
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom du champ"
|
||||
class="input input-bordered input-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select v-model="field.type" class="select select-bordered select-xs w-full">
|
||||
<option value="">Type</option>
|
||||
<option value="text">Texte</option>
|
||||
<option value="number">Nombre</option>
|
||||
<option value="select">Liste</option>
|
||||
<option value="boolean">Oui/Non</option>
|
||||
<option value="date">Date</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div 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"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-xs"
|
||||
/>
|
||||
<span class="text-xs">Obligatoire</span>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Valeur par défaut"
|
||||
class="input input-bordered input-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options pour les champs de type SELECT -->
|
||||
<div v-if="field.type === 'select'" class="mt-2">
|
||||
<textarea
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
class="textarea textarea-bordered textarea-xs w-full h-16"
|
||||
@input="updateComponentFieldOptions(component, fieldIndex)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pièces du nouveau composant -->
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-xs font-medium text-gray-600">Pièces du composant :</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="addComponentPiece(component)"
|
||||
class="btn btn-xs btn-outline"
|
||||
>
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
Ajouter pièce
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="(piece, pieceIndex) in component.pieces"
|
||||
:key="pieceIndex"
|
||||
class="border border-gray-200 rounded p-2 bg-white"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div 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>
|
||||
</svg>
|
||||
<input
|
||||
v-model="component.pieces[pieceIndex].name"
|
||||
type="text"
|
||||
:placeholder="`Pièce ${pieceIndex + 1}`"
|
||||
class="input input-bordered input-xs flex-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
@click="addCustomFieldToPiece('component', component, pieceIndex)"
|
||||
class="btn btn-xs btn-ghost"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeComponentPiece(component, pieceIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Champs personnalisés de cette pièce -->
|
||||
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-2 ml-4">
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="(field, fieldIndex) in piece.customFields"
|
||||
:key="fieldIndex"
|
||||
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>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeCustomFieldFromPiece('component', component, fieldIndex, pieceIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
<select v-model="field.type" class="select select-bordered select-xs">
|
||||
<option value="">Type</option>
|
||||
<option value="text">Texte</option>
|
||||
<option value="number">Nombre</option>
|
||||
<option value="select">Liste</option>
|
||||
<option value="boolean">Oui/Non</option>
|
||||
<option value="date">Date</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1 mt-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
v-model="field.required"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-xs"
|
||||
/>
|
||||
<span class="text-xs">Obligatoire</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Défaut"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Options pour les champs de type SELECT -->
|
||||
<div v-if="field.type === 'select'" class="mt-1">
|
||||
<textarea
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
class="textarea textarea-bordered textarea-xs w-full h-12"
|
||||
@input="updatePieceFieldOptions('component', pieceIndex, fieldIndex, component)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sous-composants -->
|
||||
<div class="ml-6 space-y-2">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-xs font-medium text-gray-600">Sous-composants :</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="addSubComponent(component)"
|
||||
class="btn btn-xs btn-outline"
|
||||
>
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
Ajouter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(subComponent, subIndex) in component.subComponents"
|
||||
:key="subIndex"
|
||||
class="border border-gray-200 rounded p-3 bg-white"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<svg class="w-3 h-3 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>
|
||||
<input
|
||||
v-model="subComponent.name"
|
||||
type="text"
|
||||
:placeholder="`Sous-composant ${subIndex + 1}`"
|
||||
class="input input-bordered input-xs flex-1"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeSubComponent(component, subIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Champs personnalisés du sous-composant -->
|
||||
<div class="mb-2">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs text-gray-500">Champs personnalisés :</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="addSubComponentCustomField(subComponent)"
|
||||
class="btn btn-xs btn-ghost"
|
||||
>
|
||||
<svg class="w-2 h-2 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="(field, fieldIndex) in subComponent.customFields"
|
||||
:key="fieldIndex"
|
||||
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>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeSubComponentCustomField(subComponent, fieldIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
<select v-model="field.type" class="select select-bordered select-xs">
|
||||
<option value="">Type</option>
|
||||
<option value="text">Texte</option>
|
||||
<option value="number">Nombre</option>
|
||||
<option value="select">Liste</option>
|
||||
<option value="boolean">Oui/Non</option>
|
||||
<option value="date">Date</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1 mt-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
v-model="field.required"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-xs"
|
||||
/>
|
||||
<span class="text-xs">Obligatoire</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Défaut"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pièces du sous-composant -->
|
||||
<div class="mb-2">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs text-gray-500">Pièces :</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="addSubComponentPiece(subComponent)"
|
||||
class="btn btn-xs btn-ghost"
|
||||
>
|
||||
<svg class="w-2 h-2 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="(piece, pieceIndex) in subComponent.pieces"
|
||||
:key="pieceIndex"
|
||||
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 gap-1">
|
||||
<svg class="w-2 h-2 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="subComponent.pieces[pieceIndex].name"
|
||||
type="text"
|
||||
:placeholder="`Pièce ${pieceIndex + 1}`"
|
||||
class="input input-bordered input-xs flex-1"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
type="button"
|
||||
@click="addCustomFieldToPiece('subComponent', subComponent, pieceIndex)"
|
||||
class="btn btn-xs btn-ghost"
|
||||
>
|
||||
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeSubComponentPiece(subComponent, pieceIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Champs personnalisés de cette pièce -->
|
||||
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-1 ml-3">
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="(field, fieldIndex) in piece.customFields"
|
||||
:key="fieldIndex"
|
||||
class="border border-gray-50 rounded p-1 bg-gray-25"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeCustomFieldFromPiece('subComponent', subComponent, fieldIndex, pieceIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
<select v-model="field.type" class="select select-bordered select-xs">
|
||||
<option value="">Type</option>
|
||||
<option value="text">Texte</option>
|
||||
<option value="number">Nombre</option>
|
||||
<option value="select">Liste</option>
|
||||
<option value="boolean">Oui/Non</option>
|
||||
<option value="date">Date</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-1 mt-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<input
|
||||
v-model="field.required"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-xs"
|
||||
/>
|
||||
<span class="text-xs">Obligatoire</span>
|
||||
</div>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Défaut"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Options pour les champs de type SELECT -->
|
||||
<div v-if="field.type === 'select'" class="mt-1">
|
||||
<textarea
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
class="textarea textarea-bordered textarea-xs w-full h-10"
|
||||
@input="updatePieceFieldOptions('subComponent', pieceIndex, fieldIndex, subComponent)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="addComponent"
|
||||
class="btn btn-outline btn-sm"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
Ajouter un composant
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const components = ref(props.modelValue)
|
||||
|
||||
// Méthodes pour les composants
|
||||
const addComponent = () => {
|
||||
components.value.push({
|
||||
name: '',
|
||||
reference: '',
|
||||
prestataire: '',
|
||||
emplacement: '',
|
||||
prix: null,
|
||||
pieces: [],
|
||||
customFields: [],
|
||||
subComponents: []
|
||||
})
|
||||
emit('update:modelValue', components.value)
|
||||
}
|
||||
|
||||
const removeComponent = (index) => {
|
||||
components.value.splice(index, 1)
|
||||
emit('update:modelValue', components.value)
|
||||
}
|
||||
|
||||
const addComponentPiece = (component) => {
|
||||
component.pieces.push({
|
||||
name: '',
|
||||
reference: '',
|
||||
prestataire: '',
|
||||
emplacement: '',
|
||||
prix: null,
|
||||
customFields: []
|
||||
})
|
||||
}
|
||||
|
||||
const removeComponentPiece = (component, index) => {
|
||||
component.pieces.splice(index, 1)
|
||||
}
|
||||
|
||||
const addComponentCustomField = (component) => {
|
||||
if (!component.customFields) {
|
||||
component.customFields = []
|
||||
}
|
||||
component.customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removeComponentCustomField = (component, index) => {
|
||||
component.customFields.splice(index, 1)
|
||||
}
|
||||
|
||||
const updateComponentFieldOptions = (component, index) => {
|
||||
if (component.customFields[index]) {
|
||||
component.customFields[index].optionsText = component.customFields[index].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes pour les sous-composants
|
||||
const addSubComponent = (component) => {
|
||||
if (!component.subComponents) {
|
||||
component.subComponents = []
|
||||
}
|
||||
component.subComponents.push({
|
||||
name: '',
|
||||
subComponents: [],
|
||||
pieces: [{ name: '', customFields: [] }],
|
||||
customFields: []
|
||||
})
|
||||
}
|
||||
|
||||
const removeSubComponent = (component, index) => {
|
||||
component.subComponents.splice(index, 1)
|
||||
}
|
||||
|
||||
const addSubComponentPiece = (subComponent) => {
|
||||
subComponent.pieces.push({ name: '', customFields: [] })
|
||||
}
|
||||
|
||||
const removeSubComponentPiece = (subComponent, index) => {
|
||||
subComponent.pieces.splice(index, 1)
|
||||
}
|
||||
|
||||
const addSubComponentCustomField = (subComponent) => {
|
||||
if (!subComponent.customFields) {
|
||||
subComponent.customFields = []
|
||||
}
|
||||
subComponent.customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removeSubComponentCustomField = (subComponent, index) => {
|
||||
subComponent.customFields.splice(index, 1)
|
||||
}
|
||||
|
||||
// Méthodes pour les champs personnalisés des pièces
|
||||
const addCustomFieldToPiece = (sourceType, sourceIndex, pieceIndex) => {
|
||||
if (sourceType === 'component') {
|
||||
if (!sourceIndex.pieces[pieceIndex].customFields) {
|
||||
sourceIndex.pieces[pieceIndex].customFields = []
|
||||
}
|
||||
sourceIndex.pieces[pieceIndex].customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
} else if (sourceType === 'subComponent') {
|
||||
if (!sourceIndex.pieces[pieceIndex].customFields) {
|
||||
sourceIndex.pieces[pieceIndex].customFields = []
|
||||
}
|
||||
sourceIndex.pieces[pieceIndex].customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const removeCustomFieldFromPiece = (sourceType, sourceIndex, fieldIndex, pieceIndex) => {
|
||||
if (sourceType === 'component') {
|
||||
if (sourceIndex.pieces[pieceIndex].customFields) {
|
||||
sourceIndex.pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
} else if (sourceType === 'subComponent') {
|
||||
if (sourceIndex.pieces[pieceIndex].customFields) {
|
||||
sourceIndex.pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatePieceFieldOptions = (sourceType, sourceIndex, fieldIndex, component) => {
|
||||
if (sourceType === 'component') {
|
||||
if (component && component.pieces[sourceIndex].customFields[fieldIndex]) {
|
||||
component.pieces[sourceIndex].customFields[fieldIndex].optionsText = component.pieces[sourceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
} else if (sourceType === 'subComponent') {
|
||||
if (component && component.pieces[sourceIndex].customFields[fieldIndex]) {
|
||||
component.pieces[sourceIndex].customFields[fieldIndex].optionsText = component.pieces[sourceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
224
app/components/TypeMachinePieceForm.vue
Normal file
224
app/components/TypeMachinePieceForm.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
@click="removePiece(index)"
|
||||
class="btn btn-square btn-error btn-sm"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div 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>
|
||||
</label>
|
||||
<input
|
||||
v-model="piece.name"
|
||||
type="text"
|
||||
placeholder="Nom de la pièce"
|
||||
class="input input-bordered input-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Référence</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="piece.reference"
|
||||
type="text"
|
||||
placeholder="Référence"
|
||||
class="input input-bordered input-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Prestataire</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="piece.prestataire"
|
||||
type="text"
|
||||
placeholder="Prestataire"
|
||||
class="input input-bordered input-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Emplacement</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="piece.emplacement"
|
||||
type="text"
|
||||
placeholder="Emplacement"
|
||||
class="input input-bordered input-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Champs personnalisés de cette pièce -->
|
||||
<div v-if="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
|
||||
v-for="(field, fieldIndex) in piece.customFields"
|
||||
:key="fieldIndex"
|
||||
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>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeCustomField(index, fieldIndex)"
|
||||
class="btn btn-square btn-error btn-xs"
|
||||
>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
<div>
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom du champ"
|
||||
class="input input-bordered input-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select v-model="field.type" class="select select-bordered select-xs w-full">
|
||||
<option value="">Type</option>
|
||||
<option value="text">Texte</option>
|
||||
<option value="number">Nombre</option>
|
||||
<option value="select">Liste</option>
|
||||
<option value="boolean">Oui/Non</option>
|
||||
<option value="date">Date</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div 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"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-xs"
|
||||
/>
|
||||
<span class="text-xs">Obligatoire</span>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Valeur par défaut"
|
||||
class="input input-bordered input-xs w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options pour les champs de type SELECT -->
|
||||
<div v-if="field.type === 'select'" class="mt-2">
|
||||
<textarea
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
class="textarea textarea-bordered textarea-xs w-full h-16"
|
||||
@input="updateFieldOptions(index, fieldIndex)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bouton pour ajouter des champs personnalisés -->
|
||||
<div class="mt-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="addCustomField(index)"
|
||||
class="btn btn-outline btn-sm"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
Ajouter champs personnalisés
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="addPiece"
|
||||
class="btn btn-outline btn-sm"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||||
</svg>
|
||||
Ajouter une pièce de machine
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const pieces = ref(props.modelValue)
|
||||
|
||||
// Méthodes pour les pièces
|
||||
const addPiece = () => {
|
||||
pieces.value.push({
|
||||
name: '',
|
||||
reference: '',
|
||||
prestataire: '',
|
||||
emplacement: '',
|
||||
prix: null,
|
||||
customFields: []
|
||||
})
|
||||
emit('update:modelValue', pieces.value)
|
||||
}
|
||||
|
||||
const removePiece = (index) => {
|
||||
pieces.value.splice(index, 1)
|
||||
emit('update:modelValue', pieces.value)
|
||||
}
|
||||
|
||||
const addCustomField = (pieceIndex) => {
|
||||
if (!pieces.value[pieceIndex].customFields) {
|
||||
pieces.value[pieceIndex].customFields = []
|
||||
}
|
||||
pieces.value[pieceIndex].customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removeCustomField = (pieceIndex, fieldIndex) => {
|
||||
if (pieces.value[pieceIndex].customFields) {
|
||||
pieces.value[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const updateFieldOptions = (pieceIndex, fieldIndex) => {
|
||||
if (pieces.value[pieceIndex].customFields[fieldIndex]) {
|
||||
pieces.value[pieceIndex].customFields[fieldIndex].optionsText = pieces.value[pieceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user