feat: Pages de détail et édition
- type/[id].vue : Page de détail et édition des types de machines - machine/[id].vue : Page de détail et édition des machines - Support du mode édition avec paramètre URL ?edit=true - Gestion des composants hiérarchiques et champs personnalisés - Interface d'édition en temps réel avec sauvegarde
This commit is contained in:
646
app/pages/machine/[id].vue
Normal file
646
app/pages/machine/[id].vue
Normal file
@@ -0,0 +1,646 @@
|
|||||||
|
<template>
|
||||||
|
<main class="container mx-auto px-6 py-8">
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div v-if="loading" class="flex justify-center items-center py-12">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Machine Details -->
|
||||||
|
<div v-else-if="machine" class="space-y-8">
|
||||||
|
<!-- Header with Edit Button -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h1 class="text-3xl font-bold">Détails de la machine</h1>
|
||||||
|
<button
|
||||||
|
@click="toggleEditMode"
|
||||||
|
class="btn btn-primary"
|
||||||
|
:class="{ 'btn-outline': isEditMode }"
|
||||||
|
>
|
||||||
|
<svg v-if="!isEditMode" 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="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>
|
||||||
|
<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="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
||||||
|
</svg>
|
||||||
|
{{ isEditMode ? 'Voir détails' : 'Modifier' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Debug info -->
|
||||||
|
<div v-if="debug" class="bg-yellow-100 p-4 rounded-lg">
|
||||||
|
<p>Debug: Machine trouvée - {{ machine.name }}</p>
|
||||||
|
<p>Components count: {{ components.length }}</p>
|
||||||
|
<p>Pieces count: {{ pieces.length }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="hero bg-gradient-to-r from-primary to-secondary min-h-[20vh] rounded-lg">
|
||||||
|
<div class="hero-content text-center text-neutral-content">
|
||||||
|
<div class="max-w-md">
|
||||||
|
<h1 class="mb-5 text-4xl font-bold">{{ machine.name }}</h1>
|
||||||
|
<p class="mb-5">{{ machine.description || machine.typeMachine?.description }}</p>
|
||||||
|
<div class="flex justify-center gap-4">
|
||||||
|
<div class="badge badge-outline">{{ machine.typeMachine?.category || 'N/A' }}</div>
|
||||||
|
<div class="badge badge-outline">{{ machine.site?.name }}</div>
|
||||||
|
<div v-if="machine.reference" class="badge badge-outline">{{ machine.reference }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Machine Info Card -->
|
||||||
|
<div class="card bg-base-100 shadow-lg">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">Informations de la machine</h2>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Nom</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="getMachineFieldId('name')"
|
||||||
|
v-model="machineName"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered"
|
||||||
|
@blur="updateMachineInfo"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered bg-base-200">
|
||||||
|
{{ machineName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Référence</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="getMachineFieldId('reference')"
|
||||||
|
v-model="machineReference"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered"
|
||||||
|
@blur="updateMachineInfo"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered bg-base-200">
|
||||||
|
{{ machineReference || 'Non définie' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Emplacement</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="getMachineFieldId('emplacement')"
|
||||||
|
v-model="machineEmplacement"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered"
|
||||||
|
@blur="updateMachineInfo"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered bg-base-200">
|
||||||
|
{{ machineEmplacement || 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Prestataire</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="isEditMode"
|
||||||
|
:id="getMachineFieldId('prestataire')"
|
||||||
|
v-model="machinePrestataire"
|
||||||
|
type="text"
|
||||||
|
class="input input-bordered"
|
||||||
|
@blur="updateMachineInfo"
|
||||||
|
/>
|
||||||
|
<div v-else class="input input-bordered bg-base-200">
|
||||||
|
{{ machinePrestataire || 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Champs personnalisés de la machine -->
|
||||||
|
<div v-if="machine && machine.customFieldValues && machine.customFieldValues.length > 0" class="mt-6 pt-4 border-t border-gray-200">
|
||||||
|
<h4 class="font-semibold text-gray-700 mb-3">Champs personnalisés de la machine</h4>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div
|
||||||
|
v-for="fieldValue in machine.customFieldValues"
|
||||||
|
:key="fieldValue.id"
|
||||||
|
class="form-control"
|
||||||
|
>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text text-sm">{{ fieldValue.customField.name }}</span>
|
||||||
|
<span v-if="fieldValue.customField.required" class="label-text-alt text-error">*</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Mode édition -->
|
||||||
|
<template v-if="isEditMode">
|
||||||
|
<!-- Champ de type TEXT -->
|
||||||
|
<input
|
||||||
|
v-if="fieldValue.customField.type === 'text'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@input="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
type="text"
|
||||||
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateMachineCustomField(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type NUMBER -->
|
||||||
|
<input
|
||||||
|
v-else-if="fieldValue.customField.type === 'number'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@input="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
type="number"
|
||||||
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateMachineCustomField(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Champ de type SELECT -->
|
||||||
|
<select
|
||||||
|
v-else-if="fieldValue.customField.type === 'select'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@change="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
class="select select-bordered select-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateMachineCustomField(fieldValue.id)"
|
||||||
|
>
|
||||||
|
<option value="">{{ fieldValue.customField.defaultValue || 'Sélectionner...' }}</option>
|
||||||
|
<option
|
||||||
|
v-for="option in fieldValue.customField.options"
|
||||||
|
:key="option"
|
||||||
|
:value="option"
|
||||||
|
>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Champ de type BOOLEAN -->
|
||||||
|
<div v-else-if="fieldValue.customField.type === 'boolean'" class="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@change="setMachineCustomFieldValue(fieldValue.id, $event.target.checked ? 'true' : 'false')"
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
:checked="fieldValue.value === 'true'"
|
||||||
|
@blur="updateMachineCustomField(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
<span class="text-sm">{{ fieldValue.value === 'true' ? 'Oui' : 'Non' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Champ de type DATE -->
|
||||||
|
<input
|
||||||
|
v-else-if="fieldValue.customField.type === 'date'"
|
||||||
|
:value="fieldValue.value"
|
||||||
|
@input="setMachineCustomFieldValue(fieldValue.id, $event.target.value)"
|
||||||
|
type="date"
|
||||||
|
:placeholder="fieldValue.customField.defaultValue || ''"
|
||||||
|
class="input input-bordered input-sm"
|
||||||
|
:required="fieldValue.customField.required"
|
||||||
|
@blur="updateMachineCustomField(fieldValue.id)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Mode lecture seule -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="input input-bordered input-sm bg-base-200">
|
||||||
|
{{ fieldValue.value || fieldValue.customField.defaultValue || 'Non défini' }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Components Section -->
|
||||||
|
<div class="card bg-base-100 shadow-lg">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="card-title">Composants</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Components Tree -->
|
||||||
|
<ComponentHierarchy
|
||||||
|
:components="components"
|
||||||
|
:is-edit-mode="isEditMode"
|
||||||
|
@update="updateComponent"
|
||||||
|
@edit-piece="updatePieceFromComponent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Machine Pieces Section -->
|
||||||
|
<div class="card bg-base-100 shadow-lg">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="card-title">Pièces de la machine</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<PieceItem
|
||||||
|
v-for="piece in machinePieces"
|
||||||
|
:key="piece.id"
|
||||||
|
:piece="piece"
|
||||||
|
:is-edit-mode="isEditMode"
|
||||||
|
@update="updatePieceInfo"
|
||||||
|
@edit="editPiece"
|
||||||
|
@custom-field-update="updatePieceCustomField"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error State -->
|
||||||
|
<div v-else class="text-center py-12">
|
||||||
|
<div class="max-w-md mx-auto">
|
||||||
|
<svg class="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.47-.881-6.08-2.33"></path>
|
||||||
|
</svg>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-2">Machine non trouvée</h3>
|
||||||
|
<p class="text-gray-500 mb-4">La machine avec l'ID "{{ machineId }}" n'existe pas ou a été supprimée.</p>
|
||||||
|
<NuxtLink to="/machines" class="btn btn-primary">
|
||||||
|
Retour aux machines
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useMachines } from '~/composables/useMachines'
|
||||||
|
import { useComposants } from '~/composables/useComposants'
|
||||||
|
import { usePieces } from '~/composables/usePieces'
|
||||||
|
import { useCustomFields } from '~/composables/useCustomFields'
|
||||||
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const machineId = route.params.id
|
||||||
|
|
||||||
|
// Vérifier que l'ID est valide
|
||||||
|
if (!machineId) {
|
||||||
|
console.error('ID de machine manquant')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
const { updateMachine: updateMachineApi } = useMachines()
|
||||||
|
const {
|
||||||
|
getComposantsByMachine,
|
||||||
|
updateComposant: updateComposantApi
|
||||||
|
} = useComposants()
|
||||||
|
const {
|
||||||
|
getPiecesByMachine,
|
||||||
|
updatePiece: updatePieceApi
|
||||||
|
} = usePieces()
|
||||||
|
|
||||||
|
const { upsertCustomFieldValue } = useCustomFields()
|
||||||
|
|
||||||
|
// Data
|
||||||
|
const loading = ref(true)
|
||||||
|
const machine = ref(null)
|
||||||
|
const components = ref([])
|
||||||
|
const pieces = ref([])
|
||||||
|
|
||||||
|
// Champs de la machine
|
||||||
|
const machineName = ref('')
|
||||||
|
const machineReference = ref('')
|
||||||
|
const machineEmplacement = ref('')
|
||||||
|
const machinePrestataire = ref('')
|
||||||
|
|
||||||
|
// Valeurs des champs personnalisés de la machine
|
||||||
|
const machineCustomFieldValues = reactive({})
|
||||||
|
|
||||||
|
// Mode d'édition
|
||||||
|
const isEditMode = ref(false)
|
||||||
|
const debug = ref(false) // Ajout de debug pour afficher les infos de debug
|
||||||
|
|
||||||
|
// Méthodes pour initialiser les champs
|
||||||
|
const initMachineFields = () => {
|
||||||
|
if (machine.value) {
|
||||||
|
machineName.value = machine.value.name || ''
|
||||||
|
machineReference.value = machine.value.reference || ''
|
||||||
|
machineEmplacement.value = machine.value.emplacement || ''
|
||||||
|
machinePrestataire.value = machine.value.prestataire || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonctions pour générer des IDs uniques
|
||||||
|
const getMachineFieldId = (fieldName) => {
|
||||||
|
return machine.value ? `machine-${fieldName}-${machine.value.id}` : `machine-${fieldName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const machinePieces = computed(() => {
|
||||||
|
const filteredPieces = pieces.value.filter(piece => !piece.composantId)
|
||||||
|
console.log('machinePieces computed:', filteredPieces)
|
||||||
|
return filteredPieces
|
||||||
|
})
|
||||||
|
|
||||||
|
const allComponents = computed(() => {
|
||||||
|
return components.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const subComponents = (parentId) => {
|
||||||
|
return components.value.filter(comp => comp.parentComposantId === parentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentPieces = (composantId) => {
|
||||||
|
return pieces.value.filter(piece => piece.composantId === composantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform custom field values to custom fields format
|
||||||
|
const transformCustomFields = (pieces) => {
|
||||||
|
return pieces.map(piece => {
|
||||||
|
const customFields = piece.customFieldValues?.map(cfv => ({
|
||||||
|
id: cfv.customField.id,
|
||||||
|
name: cfv.customField.name,
|
||||||
|
type: cfv.customField.type,
|
||||||
|
required: cfv.customField.required,
|
||||||
|
defaultValue: cfv.customField.defaultValue,
|
||||||
|
options: cfv.customField.options || [],
|
||||||
|
value: cfv.value
|
||||||
|
})) || []
|
||||||
|
|
||||||
|
return {
|
||||||
|
...piece,
|
||||||
|
customFields
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform custom fields for components (now handles nested structure)
|
||||||
|
const transformComponentCustomFields = (componentsData) => {
|
||||||
|
console.log('transformComponentCustomFields called with:', componentsData)
|
||||||
|
|
||||||
|
return componentsData.map(component => {
|
||||||
|
console.log('Processing component:', component.name, 'with sousComposants:', component.sousComposants?.length || 0)
|
||||||
|
|
||||||
|
// Transform custom fields for the current component
|
||||||
|
const customFields = component.customFieldValues?.map(cfv => ({
|
||||||
|
id: cfv.customField.id,
|
||||||
|
name: cfv.customField.name,
|
||||||
|
type: cfv.customField.type,
|
||||||
|
required: cfv.customField.required,
|
||||||
|
defaultValue: cfv.customField.defaultValue,
|
||||||
|
options: cfv.customField.options || [],
|
||||||
|
value: cfv.value
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
// Transform pieces for the current component
|
||||||
|
const pieces = component.pieces ? transformCustomFields(component.pieces) : [];
|
||||||
|
|
||||||
|
// Recursively transform sub-components (using 'sousComposants' from backend)
|
||||||
|
const subComponents = component.sousComposants ? transformComponentCustomFields(component.sousComposants) : [];
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
...component,
|
||||||
|
customFields, // Use customFields for frontend display
|
||||||
|
pieces,
|
||||||
|
subComponents // Use the transformed sousComposants as subComponents
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Transformed component:', result.name, 'with subComponents:', result.subComponents?.length || 0)
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const loadMachineData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
console.log('Début du chargement des données pour machineId:', machineId)
|
||||||
|
|
||||||
|
// Load specific machine directly from API
|
||||||
|
const { apiCall } = useApi()
|
||||||
|
console.log('Appel API pour machine:', machineId)
|
||||||
|
const machineResult = await apiCall(`/machines/${machineId}`, { method: 'GET' })
|
||||||
|
console.log('Résultat machine complet:', machineResult)
|
||||||
|
console.log('machineResult.success:', machineResult.success)
|
||||||
|
console.log('machineResult.data:', machineResult.data)
|
||||||
|
console.log('machineResult.error:', machineResult.error)
|
||||||
|
console.log('Machine customFieldValues:', machineResult.data?.customFieldValues)
|
||||||
|
console.log('Nombre de champs personnalisés:', machineResult.data?.customFieldValues?.length)
|
||||||
|
|
||||||
|
if (machineResult.success) {
|
||||||
|
machine.value = machineResult.data
|
||||||
|
console.log('Machine trouvée et assignée:', machine.value)
|
||||||
|
} else {
|
||||||
|
console.error('Machine non trouvée:', machineId)
|
||||||
|
console.error('Erreur API:', machineResult.error)
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize machine fields
|
||||||
|
initMachineFields()
|
||||||
|
console.log('Champs machine initialisés')
|
||||||
|
console.log('Machine après initialisation:', machine.value)
|
||||||
|
console.log('Machine name:', machineName.value)
|
||||||
|
console.log('Machine reference:', machineReference.value)
|
||||||
|
|
||||||
|
// Load components with hierarchy
|
||||||
|
console.log('Chargement des composants avec hiérarchie...')
|
||||||
|
const componentsResult = await apiCall(`/composants/hierarchy/${machineId}`, { method: 'GET' })
|
||||||
|
console.log('Résultat composants:', componentsResult)
|
||||||
|
console.log('Structure des données reçues:', JSON.stringify(componentsResult.data, null, 2))
|
||||||
|
if (componentsResult.success) {
|
||||||
|
console.log('Transformation des composants...')
|
||||||
|
components.value = transformComponentCustomFields(componentsResult.data)
|
||||||
|
console.log('Composants chargés:', components.value.length)
|
||||||
|
console.log('Composants transformés:', components.value)
|
||||||
|
|
||||||
|
// Debug: afficher la hiérarchie
|
||||||
|
console.log('=== HIÉRARCHIE DES COMPOSANTS ===')
|
||||||
|
components.value.forEach(comp => {
|
||||||
|
console.log(`Composant: ${comp.name} (ID: ${comp.id}, Parent: ${comp.parentComposantId})`)
|
||||||
|
if (comp.subComponents && comp.subComponents.length > 0) {
|
||||||
|
console.log(` Sous-composants: ${comp.subComponents.map(sc => sc.name).join(', ')}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log('=== FIN HIÉRARCHIE ===')
|
||||||
|
} else {
|
||||||
|
console.error('Erreur lors du chargement des composants:', componentsResult.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load pieces from machine response instead of separate API call
|
||||||
|
if (machine.value && machine.value.pieces) {
|
||||||
|
// Transformer les champs personnalisés
|
||||||
|
pieces.value = transformCustomFields(machine.value.pieces)
|
||||||
|
console.log('Pièces transformées:', pieces.value)
|
||||||
|
console.log('Pièces chargées:', pieces.value.length)
|
||||||
|
} else {
|
||||||
|
console.log('Aucune pièce trouvée dans la réponse de la machine')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Chargement terminé avec succès')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement des données:', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
console.log('Loading terminé, loading.value =', loading.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMachineInfo = async () => {
|
||||||
|
if (!machine.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await updateMachineApi(machine.value.id, {
|
||||||
|
name: machineName.value,
|
||||||
|
reference: machineReference.value,
|
||||||
|
emplacement: machineEmplacement.value,
|
||||||
|
prestataire: machinePrestataire.value
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
// Machine updated successfully
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la mise à jour de la machine:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateComponent = async (updatedComponent) => {
|
||||||
|
try {
|
||||||
|
const prixValue = updatedComponent.prix
|
||||||
|
const result = await updateComposantApi(updatedComponent.id, {
|
||||||
|
name: updatedComponent.name,
|
||||||
|
reference: updatedComponent.reference,
|
||||||
|
prestataire: updatedComponent.prestataire,
|
||||||
|
emplacement: updatedComponent.emplacement,
|
||||||
|
prix: prixValue && prixValue !== '' ? parseFloat(prixValue) : null
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
// Component updated successfully
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la mise à jour du composant:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePieceFromComponent = async (updatedPiece) => {
|
||||||
|
try {
|
||||||
|
const result = await updatePieceApi(updatedPiece.id, {
|
||||||
|
name: updatedPiece.name,
|
||||||
|
reference: updatedPiece.reference,
|
||||||
|
prestataire: updatedPiece.prestataire,
|
||||||
|
emplacement: updatedPiece.emplacement,
|
||||||
|
prix: updatedPiece.prix && updatedPiece.prix !== '' ? parseFloat(updatedPiece.prix) : null
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
// Piece updated successfully
|
||||||
|
|
||||||
|
// Si la pièce a des champs personnalisés mis à jour, les traiter
|
||||||
|
if (updatedPiece.customFields) {
|
||||||
|
for (const field of updatedPiece.customFields) {
|
||||||
|
if (field.value !== undefined) {
|
||||||
|
await upsertCustomFieldValue(
|
||||||
|
field.id,
|
||||||
|
'piece',
|
||||||
|
updatedPiece.id,
|
||||||
|
field.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la mise à jour de la pièce:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePieceInfo = async (updatedPiece) => {
|
||||||
|
try {
|
||||||
|
const result = await updatePieceApi(updatedPiece.id, {
|
||||||
|
name: updatedPiece.name,
|
||||||
|
reference: updatedPiece.reference,
|
||||||
|
prestataire: updatedPiece.prestataire,
|
||||||
|
emplacement: updatedPiece.emplacement,
|
||||||
|
prix: updatedPiece.prix && updatedPiece.prix !== '' ? parseFloat(updatedPiece.prix) : null
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
// Piece updated successfully
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la mise à jour de la pièce:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Méthodes pour les champs personnalisés de la machine
|
||||||
|
const setMachineCustomFieldValue = (fieldValueId, value) => {
|
||||||
|
const fieldValue = machine.value?.customFieldValues?.find(fv => fv.id === fieldValueId)
|
||||||
|
if (fieldValue) {
|
||||||
|
fieldValue.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateMachineCustomField = async (fieldValueId) => {
|
||||||
|
const fieldValue = machine.value?.customFieldValues?.find(fv => fv.id === fieldValueId)
|
||||||
|
if (fieldValue) {
|
||||||
|
const { updateCustomFieldValue } = useCustomFields()
|
||||||
|
const { showSuccess, showError } = useToast()
|
||||||
|
|
||||||
|
const result = await updateCustomFieldValue(fieldValueId, { value: fieldValue.value })
|
||||||
|
if (result.success) {
|
||||||
|
showSuccess(`Champ "${fieldValue.customField.name}" de la machine mis à jour avec succès`)
|
||||||
|
} else {
|
||||||
|
showError(`Erreur lors de la mise à jour du champ "${fieldValue.customField.name}"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePieceCustomField = async (fieldUpdate) => {
|
||||||
|
const { showSuccess, showError } = useToast()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await upsertCustomFieldValue(
|
||||||
|
fieldUpdate.fieldId,
|
||||||
|
'piece',
|
||||||
|
fieldUpdate.pieceId,
|
||||||
|
fieldUpdate.value
|
||||||
|
)
|
||||||
|
if (result.success) {
|
||||||
|
showSuccess(`Champ personnalisé mis à jour avec succès`)
|
||||||
|
} else {
|
||||||
|
showError(`Erreur lors de la mise à jour du champ personnalisé`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError(`Erreur lors de la mise à jour du champ personnalisé`)
|
||||||
|
console.error('Erreur lors de la mise à jour du champ personnalisé:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const editComponent = (component) => {
|
||||||
|
// TODO: Implement edit modal
|
||||||
|
console.log('Edit component:', component)
|
||||||
|
}
|
||||||
|
|
||||||
|
const editPiece = (piece) => {
|
||||||
|
// TODO: Implement edit modal
|
||||||
|
console.log('Edit piece:', piece)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleEditMode = () => {
|
||||||
|
isEditMode.value = !isEditMode.value
|
||||||
|
debug.value = !debug.value // Inversez la valeur de debug
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
onMounted(() => {
|
||||||
|
loadMachineData()
|
||||||
|
|
||||||
|
// Vérifier si on doit activer le mode édition depuis l'URL
|
||||||
|
const route = useRoute()
|
||||||
|
if (route.query.edit === 'true') {
|
||||||
|
isEditMode.value = true
|
||||||
|
console.log('Mode édition activé depuis l\'URL')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
249
app/pages/type/[id].vue
Normal file
249
app/pages/type/[id].vue
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<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">
|
||||||
|
Ajoutez des éléments au type de machine existant.
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<!-- Current Type Info -->
|
||||||
|
<TypeInfoDisplay :type="type" />
|
||||||
|
|
||||||
|
<!-- Affichage des composants existants -->
|
||||||
|
<div v-if="type.components && type.components.length > 0" class="mb-8">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Composants existants</h3>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<TypeComponentDisplay
|
||||||
|
v-for="(component, componentIndex) in type.components"
|
||||||
|
:key="componentIndex"
|
||||||
|
:component="component"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Affichage des pièces principales existantes -->
|
||||||
|
<div v-if="type.machinePieces && type.machinePieces.length > 0" class="mb-8">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Pièces principales existantes</h3>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<TypeMachinePieceDisplay
|
||||||
|
v-for="(piece, pieceIndex) in type.machinePieces"
|
||||||
|
:key="pieceIndex"
|
||||||
|
:piece="piece"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form @submit.prevent="saveChanges" class="space-y-6">
|
||||||
|
<!-- Add Machine Pieces -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Ajouter des pièces de machine</span>
|
||||||
|
<span class="label-text-alt">Nouvelles pièces directement attachées à la machine</span>
|
||||||
|
</label>
|
||||||
|
<TypeMachinePieceForm v-model="newMachinePieces" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add Components -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Ajouter des composants</span>
|
||||||
|
<span class="label-text-alt">Nouveaux composants principaux avec sous-composants</span>
|
||||||
|
</label>
|
||||||
|
<TypeComponentForm v-model="newComponents" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="card-actions justify-end">
|
||||||
|
<button type="button" @click="resetForm" class="btn btn-outline">
|
||||||
|
Réinitialiser
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||||
|
<svg v-if="saving" class="w-5 h-5 mr-2 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||||
|
</svg>
|
||||||
|
<svg v-else class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||||
|
</svg>
|
||||||
|
{{ saving ? 'Sauvegarde...' : 'Sauvegarder les modifications' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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)
|
||||||
|
|
||||||
|
// Nouvelles données à ajouter
|
||||||
|
const newMachinePieces = ref([])
|
||||||
|
const newComponents = ref([])
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
newMachinePieces.value = []
|
||||||
|
newComponents.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveChanges = async () => {
|
||||||
|
try {
|
||||||
|
saving.value = true
|
||||||
|
|
||||||
|
// Préparer les données mises à jour
|
||||||
|
const updatedType = {
|
||||||
|
...type.value,
|
||||||
|
machinePieces: [
|
||||||
|
...(type.value.machinePieces || []),
|
||||||
|
...newMachinePieces.value
|
||||||
|
.filter(piece => piece.name.trim() !== '')
|
||||||
|
.map(piece => ({
|
||||||
|
name: piece.name,
|
||||||
|
reference: piece.reference,
|
||||||
|
prestataire: piece.prestataire,
|
||||||
|
emplacement: piece.emplacement,
|
||||||
|
prix: piece.prix,
|
||||||
|
customFields: piece.customFields || []
|
||||||
|
}))
|
||||||
|
],
|
||||||
|
components: [
|
||||||
|
...(type.value.components || []),
|
||||||
|
...newComponents.value
|
||||||
|
.filter(comp => comp.name.trim() !== '')
|
||||||
|
.map(comp => ({
|
||||||
|
name: comp.name,
|
||||||
|
reference: comp.reference,
|
||||||
|
prestataire: comp.prestataire,
|
||||||
|
emplacement: comp.emplacement,
|
||||||
|
prix: comp.prix,
|
||||||
|
customFields: comp.customFields || [],
|
||||||
|
pieces: comp.pieces
|
||||||
|
.filter(piece => piece.name.trim() !== '')
|
||||||
|
.map(piece => ({
|
||||||
|
name: piece.name,
|
||||||
|
reference: piece.reference,
|
||||||
|
prestataire: piece.prestataire,
|
||||||
|
emplacement: piece.emplacement,
|
||||||
|
prix: piece.prix,
|
||||||
|
customFields: piece.customFields || []
|
||||||
|
})),
|
||||||
|
subComponents: comp.subComponents
|
||||||
|
.filter(subComp => subComp.name.trim() !== '')
|
||||||
|
.map(subComp => ({
|
||||||
|
name: subComp.name,
|
||||||
|
reference: subComp.reference,
|
||||||
|
prestataire: subComp.prestataire,
|
||||||
|
emplacement: subComp.emplacement,
|
||||||
|
prix: subComp.prix,
|
||||||
|
customFields: subComp.customFields || [],
|
||||||
|
pieces: subComp.pieces
|
||||||
|
.filter(piece => piece.name.trim() !== '')
|
||||||
|
.map(piece => ({
|
||||||
|
name: piece.name,
|
||||||
|
reference: piece.reference,
|
||||||
|
prestataire: piece.prestataire,
|
||||||
|
emplacement: piece.emplacement,
|
||||||
|
prix: piece.prix,
|
||||||
|
customFields: piece.customFields || []
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await updateMachineType(type.value.id, updatedType)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showSuccess('Type mis à jour avec succès !')
|
||||||
|
router.push('/types')
|
||||||
|
} else {
|
||||||
|
showError('Erreur lors de la mise à jour du type')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de la sauvegarde:', error)
|
||||||
|
showError('Erreur lors de la sauvegarde')
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charger le type au montage
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const typeId = route.params.id
|
||||||
|
console.log('=== EDIT PAGE LOADING ===')
|
||||||
|
console.log('Loading type with ID:', typeId)
|
||||||
|
console.log('Route params:', route.params)
|
||||||
|
console.log('Current route:', route.path)
|
||||||
|
|
||||||
|
const result = await getMachineTypeById(typeId)
|
||||||
|
console.log('API Result:', result)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
type.value = result.data
|
||||||
|
console.log('Type loaded successfully:', type.value)
|
||||||
|
console.log('Type ID:', type.value.id)
|
||||||
|
console.log('Type name:', type.value.name)
|
||||||
|
} 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>
|
||||||
Reference in New Issue
Block a user