feat: ajout du formulaire de modification complète des types de machines
- Création du composant TypeEditForm.vue pour l'édition complète des types - Ajout de la page /type/edit/[id].vue pour l'édition complète - Ajout des boutons d'édition dans les pages types.vue et type/[id].vue - Gestion complète des champs personnalisés, pièces et composants - Interface intuitive avec ajout/suppression dynamique d'éléments
This commit is contained in:
859
app/components/TypeEditForm.vue
Normal file
859
app/components/TypeEditForm.vue
Normal file
@@ -0,0 +1,859 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- Informations de base du type -->
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title text-lg mb-4">Informations de base</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Nom du type</span>
|
||||
<span class="label-text-alt text-error">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="formData.name"
|
||||
type="text"
|
||||
placeholder="Nom du type de machine"
|
||||
class="input input-bordered"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Catégorie</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="formData.category"
|
||||
type="text"
|
||||
placeholder="Catégorie du type"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Description</span>
|
||||
</label>
|
||||
<textarea
|
||||
v-model="formData.description"
|
||||
placeholder="Description du type de machine"
|
||||
class="textarea textarea-bordered h-24"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Fréquence de maintenance</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="formData.maintenanceFrequency"
|
||||
type="text"
|
||||
placeholder="ex: Mensuelle, Trimestrielle"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Champs personnalisés du type -->
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="card-title text-lg">Champs personnalisés du type</h3>
|
||||
<button
|
||||
type="button"
|
||||
@click="addCustomField"
|
||||
class="btn btn-primary 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 champ
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(field, fieldIndex) in formData.customFields"
|
||||
:key="fieldIndex"
|
||||
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">Champ personnalisé {{ fieldIndex + 1 }}</h5>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeCustomField(fieldIndex)"
|
||||
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 du champ</span>
|
||||
<span class="label-text-alt text-error">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom du champ"
|
||||
class="input input-bordered input-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Type de champ</span>
|
||||
<span class="label-text-alt text-error">*</span>
|
||||
</label>
|
||||
<select v-model="field.type" class="select select-bordered select-sm" required>
|
||||
<option value="">Sélectionner un type</option>
|
||||
<option value="text">Texte</option>
|
||||
<option value="number">Nombre</option>
|
||||
<option value="select">Liste déroulante</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-4 mt-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="field.required"
|
||||
type="checkbox"
|
||||
class="checkbox checkbox-sm"
|
||||
/>
|
||||
<span class="text-sm">Champ obligatoire</span>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Valeur par défaut</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Valeur par défaut"
|
||||
class="input input-bordered input-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Options pour les champs de type SELECT -->
|
||||
<div v-if="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>
|
||||
</label>
|
||||
<textarea
|
||||
v-model="field.optionsText"
|
||||
placeholder="Option 1 Option 2 Option 3"
|
||||
class="textarea textarea-bordered textarea-sm w-full h-20"
|
||||
@input="updateFieldOptions(fieldIndex)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pièces principales du type -->
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="card-title text-lg">Pièces principales</h3>
|
||||
<button
|
||||
type="button"
|
||||
@click="addMachinePiece"
|
||||
class="btn btn-primary 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
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(piece, pieceIndex) in formData.machinePieces"
|
||||
:key="pieceIndex"
|
||||
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">Pièce {{ pieceIndex + 1 }}</h5>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeMachinePiece(pieceIndex)"
|
||||
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>
|
||||
<span class="label-text-alt text-error">*</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 la pièce -->
|
||||
<div 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
|
||||
type="button"
|
||||
@click="addPieceCustomField(pieceIndex)"
|
||||
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 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="removePieceCustomField(pieceIndex, 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">
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom du champ"
|
||||
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-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>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Valeur par défaut"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
</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="updatePieceFieldOptions(pieceIndex, fieldIndex)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Composants du type -->
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="card-title text-lg">Composants</h3>
|
||||
<button
|
||||
type="button"
|
||||
@click="addComponent"
|
||||
class="btn btn-primary 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>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(component, componentIndex) in formData.components"
|
||||
:key="componentIndex"
|
||||
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">Composant {{ componentIndex + 1 }}</h5>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeComponent(componentIndex)"
|
||||
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 du composant</span>
|
||||
<span class="label-text-alt text-error">*</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="mt-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-sm font-medium">Champs personnalisés du composant :</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="addComponentCustomField(componentIndex)"
|
||||
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 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 {{ fieldIndex + 1 }}</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeComponentCustomField(componentIndex, 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">
|
||||
<input
|
||||
v-model="field.name"
|
||||
type="text"
|
||||
placeholder="Nom du champ"
|
||||
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-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>
|
||||
<input
|
||||
v-model="field.defaultValue"
|
||||
type="text"
|
||||
placeholder="Valeur par défaut"
|
||||
class="input input-bordered input-xs"
|
||||
/>
|
||||
</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(componentIndex, fieldIndex)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pièces du composant -->
|
||||
<div class="mt-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-sm font-medium">Pièces du composant :</span>
|
||||
<button
|
||||
type="button"
|
||||
@click="addComponentPiece(componentIndex)"
|
||||
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 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="piece.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="addPieceCustomFieldToComponent(componentIndex, 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(componentIndex, 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="removePieceCustomFieldFromComponent(componentIndex, pieceIndex, 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>
|
||||
|
||||
<!-- 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="updateComponentPieceFieldOptions(componentIndex, pieceIndex, fieldIndex)"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="card bg-base-100 shadow-lg">
|
||||
<div class="card-body">
|
||||
<div class="card-actions justify-end">
|
||||
<button type="button" @click="resetForm" class="btn btn-outline">
|
||||
Réinitialiser
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
<svg v-if="saving" class="w-5 h-5 mr-2 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<svg v-else class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
{{ saving ? 'Sauvegarde...' : 'Sauvegarder les modifications' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
saving: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'submit'])
|
||||
|
||||
// Initialiser les données du formulaire
|
||||
const formData = reactive({
|
||||
name: props.modelValue?.name || '',
|
||||
description: props.modelValue?.description || '',
|
||||
category: props.modelValue?.category || '',
|
||||
maintenanceFrequency: props.modelValue?.maintenanceFrequency || '',
|
||||
customFields: props.modelValue?.customFields || [],
|
||||
machinePieces: props.modelValue?.machinePieces || [],
|
||||
components: props.modelValue?.components || []
|
||||
})
|
||||
|
||||
// Surveiller les changements et émettre les mises à jour
|
||||
watch(formData, (newValue) => {
|
||||
emit('update:modelValue', newValue)
|
||||
}, { deep: true })
|
||||
|
||||
// Méthodes pour les champs personnalisés
|
||||
const addCustomField = () => {
|
||||
formData.customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removeCustomField = (index) => {
|
||||
formData.customFields.splice(index, 1)
|
||||
}
|
||||
|
||||
const updateFieldOptions = (fieldIndex) => {
|
||||
if (formData.customFields[fieldIndex]) {
|
||||
formData.customFields[fieldIndex].optionsText = formData.customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes pour les pièces principales
|
||||
const addMachinePiece = () => {
|
||||
formData.machinePieces.push({
|
||||
name: '',
|
||||
reference: '',
|
||||
prestataire: '',
|
||||
emplacement: '',
|
||||
prix: null,
|
||||
customFields: []
|
||||
})
|
||||
}
|
||||
|
||||
const removeMachinePiece = (index) => {
|
||||
formData.machinePieces.splice(index, 1)
|
||||
}
|
||||
|
||||
const addPieceCustomField = (pieceIndex) => {
|
||||
if (!formData.machinePieces[pieceIndex].customFields) {
|
||||
formData.machinePieces[pieceIndex].customFields = []
|
||||
}
|
||||
formData.machinePieces[pieceIndex].customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removePieceCustomField = (pieceIndex, fieldIndex) => {
|
||||
if (formData.machinePieces[pieceIndex].customFields) {
|
||||
formData.machinePieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const updatePieceFieldOptions = (pieceIndex, fieldIndex) => {
|
||||
if (formData.machinePieces[pieceIndex]?.customFields?.[fieldIndex]) {
|
||||
formData.machinePieces[pieceIndex].customFields[fieldIndex].optionsText = formData.machinePieces[pieceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes pour les composants
|
||||
const addComponent = () => {
|
||||
formData.components.push({
|
||||
name: '',
|
||||
reference: '',
|
||||
prestataire: '',
|
||||
emplacement: '',
|
||||
prix: null,
|
||||
customFields: [],
|
||||
pieces: []
|
||||
})
|
||||
}
|
||||
|
||||
const removeComponent = (index) => {
|
||||
formData.components.splice(index, 1)
|
||||
}
|
||||
|
||||
const addComponentCustomField = (componentIndex) => {
|
||||
if (!formData.components[componentIndex].customFields) {
|
||||
formData.components[componentIndex].customFields = []
|
||||
}
|
||||
formData.components[componentIndex].customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removeComponentCustomField = (componentIndex, fieldIndex) => {
|
||||
if (formData.components[componentIndex].customFields) {
|
||||
formData.components[componentIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const updateComponentFieldOptions = (componentIndex, fieldIndex) => {
|
||||
if (formData.components[componentIndex]?.customFields?.[fieldIndex]) {
|
||||
formData.components[componentIndex].customFields[fieldIndex].optionsText = formData.components[componentIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
const addComponentPiece = (componentIndex) => {
|
||||
if (!formData.components[componentIndex].pieces) {
|
||||
formData.components[componentIndex].pieces = []
|
||||
}
|
||||
formData.components[componentIndex].pieces.push({
|
||||
name: '',
|
||||
reference: '',
|
||||
prestataire: '',
|
||||
emplacement: '',
|
||||
prix: null,
|
||||
customFields: []
|
||||
})
|
||||
}
|
||||
|
||||
const removeComponentPiece = (componentIndex, pieceIndex) => {
|
||||
if (formData.components[componentIndex].pieces) {
|
||||
formData.components[componentIndex].pieces.splice(pieceIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const addPieceCustomFieldToComponent = (componentIndex, pieceIndex) => {
|
||||
if (!formData.components[componentIndex].pieces[pieceIndex].customFields) {
|
||||
formData.components[componentIndex].pieces[pieceIndex].customFields = []
|
||||
}
|
||||
formData.components[componentIndex].pieces[pieceIndex].customFields.push({
|
||||
name: '',
|
||||
type: '',
|
||||
required: false,
|
||||
defaultValue: '',
|
||||
optionsText: ''
|
||||
})
|
||||
}
|
||||
|
||||
const removePieceCustomFieldFromComponent = (componentIndex, pieceIndex, fieldIndex) => {
|
||||
if (formData.components[componentIndex]?.pieces?.[pieceIndex]?.customFields) {
|
||||
formData.components[componentIndex].pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const updateComponentPieceFieldOptions = (componentIndex, pieceIndex, fieldIndex) => {
|
||||
if (formData.components[componentIndex]?.pieces?.[pieceIndex]?.customFields?.[fieldIndex]) {
|
||||
formData.components[componentIndex].pieces[pieceIndex].customFields[fieldIndex].optionsText = formData.components[componentIndex].pieces[pieceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode de réinitialisation
|
||||
const resetForm = () => {
|
||||
Object.assign(formData, {
|
||||
name: props.modelValue?.name || '',
|
||||
description: props.modelValue?.description || '',
|
||||
category: props.modelValue?.category || '',
|
||||
maintenanceFrequency: props.modelValue?.maintenanceFrequency || '',
|
||||
customFields: props.modelValue?.customFields || [],
|
||||
machinePieces: props.modelValue?.machinePieces || [],
|
||||
components: props.modelValue?.components || []
|
||||
})
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user