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:
Matthieu
2025-07-31 17:42:28 +02:00
parent 74b78137a0
commit c33a04b68e
4 changed files with 1097 additions and 3 deletions

View 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&#10;Option 2&#10;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&#10;Option 2&#10;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&#10;Option 2&#10;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&#10;Option 2&#10;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>

View File

@@ -26,9 +26,17 @@
<h2 class="card-title text-2xl">
Modifier : {{ type.name }}
</h2>
<NuxtLink to="/types" class="btn btn-outline">
Retour
</NuxtLink>
<div class="flex gap-2">
<NuxtLink :to="`/type/edit/${type.id}`" class="btn btn-secondary">
<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="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
Éditer complètement
</NuxtLink>
<NuxtLink to="/types" class="btn btn-outline">
Retour
</NuxtLink>
</div>
</div>
<!-- Current Type Info -->

View File

@@ -0,0 +1,221 @@
<template>
<main class="container mx-auto px-6 py-8">
<!-- Hero Section -->
<div class="hero min-h-[30vh] bg-gradient-to-r from-secondary to-primary">
<div class="hero-content text-center text-neutral-content">
<div class="max-w-md">
<h1 class="mb-5 text-4xl font-bold">Modifier le Type</h1>
<p class="mb-5">
Modifiez les informations du type de machine.
</p>
</div>
</div>
</div>
<!-- Loading State -->
<div v-if="loading" class="my-8 text-center">
<div class="loading loading-spinner loading-lg"></div>
<p class="mt-4 text-gray-600">Chargement du type...</p>
</div>
<!-- Edit Form -->
<div v-else-if="type" class="my-8">
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex items-center justify-between mb-6">
<h2 class="card-title text-2xl">
Modifier : {{ type.name }}
</h2>
<NuxtLink to="/types" class="btn btn-outline">
Retour
</NuxtLink>
</div>
<form @submit.prevent="saveChanges">
<TypeEditForm
v-model="editedType"
:saving="saving"
@submit="saveChanges"
/>
</form>
</div>
</div>
</div>
<!-- Error State -->
<div v-else class="my-8 text-center">
<div class="alert alert-error">
<div>
<h3 class="font-bold">Type non trouvé</h3>
<p>Le type de machine demandé n'existe pas.</p>
</div>
</div>
<NuxtLink to="/types" class="btn btn-primary mt-4">
Retour aux types
</NuxtLink>
</div>
</main>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useToast } from '~/composables/useToast'
const route = useRoute()
const router = useRouter()
const { getMachineTypeById, updateMachineType } = useMachineTypesApi()
const { showSuccess, showError } = useToast()
const type = ref(null)
const loading = ref(true)
const saving = ref(false)
// Données éditées du type
const editedType = reactive({
name: '',
description: '',
category: '',
maintenanceFrequency: '',
customFields: [],
machinePieces: [],
components: []
})
const saveChanges = async () => {
try {
saving.value = true
// Préparer les données pour l'API
const updatedType = {
...editedType,
// Traiter les champs personnalisés
customFields: editedType.customFields
.filter(field => field.name.trim() !== '')
.map(field => ({
name: field.name,
type: field.type,
required: field.required || false,
defaultValue: field.defaultValue || '',
options: field.type === 'select' && field.optionsText
? field.optionsText.split('\n').filter(opt => opt.trim() !== '')
: []
})),
// Traiter les pièces principales
machinePieces: editedType.machinePieces
.filter(piece => piece.name.trim() !== '')
.map(piece => ({
name: piece.name,
reference: piece.reference || '',
prestataire: piece.prestataire || '',
emplacement: piece.emplacement || '',
prix: piece.prix || null,
customFields: (piece.customFields || [])
.filter(field => field.name.trim() !== '')
.map(field => ({
name: field.name,
type: field.type,
required: field.required || false,
defaultValue: field.defaultValue || '',
options: field.type === 'select' && field.optionsText
? field.optionsText.split('\n').filter(opt => opt.trim() !== '')
: []
}))
})),
// Traiter les composants
components: editedType.components
.filter(comp => comp.name.trim() !== '')
.map(comp => ({
name: comp.name,
reference: comp.reference || '',
prestataire: comp.prestataire || '',
emplacement: comp.emplacement || '',
prix: comp.prix || null,
customFields: (comp.customFields || [])
.filter(field => field.name.trim() !== '')
.map(field => ({
name: field.name,
type: field.type,
required: field.required || false,
defaultValue: field.defaultValue || '',
options: field.type === 'select' && field.optionsText
? field.optionsText.split('\n').filter(opt => opt.trim() !== '')
: []
})),
pieces: (comp.pieces || [])
.filter(piece => piece.name.trim() !== '')
.map(piece => ({
name: piece.name,
reference: piece.reference || '',
prestataire: piece.prestataire || '',
emplacement: piece.emplacement || '',
prix: piece.prix || null,
customFields: (piece.customFields || [])
.filter(field => field.name.trim() !== '')
.map(field => ({
name: field.name,
type: field.type,
required: field.required || false,
defaultValue: field.defaultValue || '',
options: field.type === 'select' && field.optionsText
? field.optionsText.split('\n').filter(opt => opt.trim() !== '')
: []
}))
}))
}))
}
const result = await updateMachineType(type.value.id, updatedType)
if (result.success) {
showSuccess('Type mis à jour avec succès !')
router.push('/types')
} else {
showError('Erreur lors de la mise à jour du type')
}
} catch (error) {
console.error('Erreur lors de la sauvegarde:', error)
showError('Erreur lors de la sauvegarde')
} finally {
saving.value = false
}
}
// Charger le type au montage
onMounted(async () => {
try {
const typeId = route.params.id
console.log('=== EDIT TYPE PAGE LOADING ===')
console.log('Loading type with ID:', typeId)
const result = await getMachineTypeById(typeId)
console.log('API Result:', result)
if (result.success) {
type.value = result.data
console.log('Type loaded successfully:', type.value)
// Initialiser les données éditées
Object.assign(editedType, {
name: type.value.name || '',
description: type.value.description || '',
category: type.value.category || '',
maintenanceFrequency: type.value.maintenanceFrequency || '',
customFields: type.value.customFields || [],
machinePieces: type.value.machinePieces || [],
components: type.value.components || []
})
} else {
console.error('Failed to load type:', result.error)
showError('Type non trouvé')
}
} catch (error) {
console.error('Erreur lors du chargement:', error)
showError('Erreur lors du chargement')
} finally {
loading.value = false
console.log('Loading finished, loading.value:', loading.value)
}
})
</script>

View File

@@ -76,6 +76,12 @@
Supprimer
</button>
<NuxtLink :to="`/type/${type.id}`" class="btn btn-sm btn-outline">Voir détails</NuxtLink>
<NuxtLink :to="`/type/edit/${type.id}`" class="btn btn-sm btn-secondary">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
</svg>
Modifier
</NuxtLink>
<button class="btn btn-sm btn-primary">Utiliser</button>
</div>
</div>