1556 lines
74 KiB
Vue
1556 lines
74 KiB
Vue
<template>
|
|
<main class="container mx-auto px-6 py-8">
|
|
<!-- Hero Section -->
|
|
<div class="hero min-h-[30vh] bg-gradient-to-r from-primary to-secondary">
|
|
<div class="hero-content text-center text-neutral-content">
|
|
<div class="max-w-md">
|
|
<h1 class="mb-5 text-4xl font-bold">Générateur de Types</h1>
|
|
<p class="mb-5">
|
|
Créez des types de machines avec une hiérarchie infinie de composants et pièces.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Generator Form -->
|
|
<div class="my-8">
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body">
|
|
<h2 class="card-title mb-6">
|
|
<svg class="w-6 h-6" 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>
|
|
Nouveau Type de Machine
|
|
</h2>
|
|
|
|
<form @submit.prevent="generateType" class="space-y-6">
|
|
<div class="flex justify-end">
|
|
<button
|
|
type="button"
|
|
class="btn btn-outline btn-sm"
|
|
@click="toggleAllSections"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 mr-2"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
v-if="allSectionsExpanded"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M18 12H6"
|
|
></path>
|
|
<path
|
|
v-else
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 6v12m6-6H6"
|
|
></path>
|
|
</svg>
|
|
{{ allSectionsExpanded ? 'Tout plier' : 'Tout déplier' }}
|
|
</button>
|
|
</div>
|
|
<!-- Basic Information -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Nom du type</span>
|
|
</label>
|
|
<input
|
|
v-model="newType.name"
|
|
type="text"
|
|
placeholder="Ex: Presse hydraulique"
|
|
class="input input-bordered"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Catégorie</span>
|
|
</label>
|
|
<select v-model="newType.category" class="select select-bordered" required>
|
|
<option value="">Sélectionner une catégorie</option>
|
|
<option v-for="category in categories" :key="category" :value="category">
|
|
{{ category }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Description</span>
|
|
</label>
|
|
<textarea
|
|
v-model="newType.description"
|
|
placeholder="Description détaillée du type de machine..."
|
|
class="textarea textarea-bordered h-24"
|
|
required
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Fréquence de maintenance</span>
|
|
</label>
|
|
<select v-model="newType.maintenanceFrequency" class="select select-bordered" required>
|
|
<option value="">Sélectionner une fréquence</option>
|
|
<option value="Quotidienne">Quotidienne</option>
|
|
<option value="Hebdomadaire">Hebdomadaire</option>
|
|
<option value="Mensuelle">Mensuelle</option>
|
|
<option value="Trimestrielle">Trimestrielle</option>
|
|
<option value="Semestrielle">Semestrielle</option>
|
|
<option value="Annuelle">Annuelle</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Machine Pieces (Root Level) -->
|
|
<div class="form-control">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm p-1"
|
|
@click="toggleSection('machinePieces')"
|
|
title="Plier / déplier les pièces"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 transition-transform duration-200"
|
|
:class="{ 'rotate-90': sections.machinePieces }"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
</svg>
|
|
</button>
|
|
<label class="label m-0 flex flex-col items-start">
|
|
<span class="label-text">Pièces de la machine</span>
|
|
<span class="label-text-alt">Pièces directement attachées à la machine (optionnel)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div v-if="sections.machinePieces" class="space-y-4">
|
|
<div v-for="(piece, index) in newType.machinePieces" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h5 class="text-sm font-medium">Pièce {{ index + 1 }}</h5>
|
|
<button
|
|
type="button"
|
|
@click="removeMachinePiece(index)"
|
|
class="btn btn-square btn-error btn-sm"
|
|
v-if="newType.machinePieces.length > 0"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-3">
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Nom de la pièce</span>
|
|
</label>
|
|
<input
|
|
v-model="newType.machinePieces[index].name"
|
|
type="text"
|
|
:placeholder="`Pièce de machine ${index + 1}`"
|
|
class="input input-bordered input-sm"
|
|
/>
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Actions</span>
|
|
</label>
|
|
<button
|
|
type="button"
|
|
@click="addCustomFieldToPiece('machine', index, null)"
|
|
class="btn btn-outline btn-sm"
|
|
>
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
Ajouter champs personnalisés
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Champs personnalisés de cette pièce -->
|
|
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-4">
|
|
<h6 class="text-xs font-medium text-gray-600 mb-2">Champs personnalisés de cette pièce :</h6>
|
|
<div class="space-y-2">
|
|
<div
|
|
v-for="(field, fieldIndex) in piece.customFields"
|
|
:key="fieldIndex"
|
|
class="border border-gray-200 rounded p-2 bg-white"
|
|
>
|
|
<div class="flex items-center justify-between mb-2">
|
|
<span class="text-xs font-medium">Champ {{ fieldIndex + 1 }}</span>
|
|
<button
|
|
type="button"
|
|
@click="removeCustomFieldFromPiece('machine', index, fieldIndex, null)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
<div>
|
|
<input
|
|
v-model="field.name"
|
|
type="text"
|
|
placeholder="Nom du champ"
|
|
class="input input-bordered input-xs w-full"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<select v-model="field.type" class="select select-bordered select-xs w-full">
|
|
<option value="">Type</option>
|
|
<option value="text">Texte</option>
|
|
<option value="number">Nombre</option>
|
|
<option value="select">Liste</option>
|
|
<option value="boolean">Oui/Non</option>
|
|
<option value="date">Date</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
|
|
<div class="flex items-center gap-2">
|
|
<input
|
|
v-model="field.required"
|
|
type="checkbox"
|
|
class="checkbox checkbox-xs"
|
|
/>
|
|
<span class="text-xs">Obligatoire</span>
|
|
</div>
|
|
<div>
|
|
<input
|
|
v-model="field.defaultValue"
|
|
type="text"
|
|
placeholder="Valeur par défaut"
|
|
class="input input-bordered input-xs w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Options pour les champs de type SELECT -->
|
|
<div v-if="field.type === 'select'" class="mt-2">
|
|
<textarea
|
|
v-model="field.optionsText"
|
|
placeholder="Option 1 Option 2 Option 3"
|
|
class="textarea textarea-bordered textarea-xs w-full h-16"
|
|
@input="updatePieceFieldOptions('machine', index, fieldIndex, null)"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
@click="addMachinePiece"
|
|
class="btn btn-outline btn-sm"
|
|
>
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
Ajouter une pièce de machine
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hierarchical Components -->
|
|
<div class="form-control">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm p-1"
|
|
@click="toggleSection('components')"
|
|
title="Plier / déplier la structure"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 transition-transform duration-200"
|
|
:class="{ 'rotate-90': sections.components }"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
</svg>
|
|
</button>
|
|
<label class="label m-0 flex flex-col items-start">
|
|
<span class="label-text">Structure hiérarchique</span>
|
|
<span class="label-text-alt">Définissez les composants, sous-composants et leurs pièces (optionnel)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="sections.components" class="space-y-4">
|
|
<!-- Root Components -->
|
|
<div class="space-y-3">
|
|
<h4 class="text-sm font-semibold text-gray-700">Composants principaux :</h4>
|
|
<div
|
|
v-for="(component, index) in newType.components"
|
|
:key="index"
|
|
class="border border-gray-200 rounded-lg p-4 bg-gray-50"
|
|
>
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-xs p-1"
|
|
@click="toggleComponentDetails(index)"
|
|
title="Plier / déplier le composant"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 transition-transform duration-200"
|
|
:class="{ 'rotate-90': isComponentExpanded(index) }"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
</svg>
|
|
</button>
|
|
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path>
|
|
</svg>
|
|
<input
|
|
v-model="component.name"
|
|
type="text"
|
|
:placeholder="`Composant ${index + 1}`"
|
|
class="input input-bordered input-sm flex-1"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="removeComponent(index)"
|
|
class="btn btn-square btn-error btn-sm"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="isComponentExpanded(index)">
|
|
|
|
<!-- Champs personnalisés du composant -->
|
|
<div class="mb-3">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<span class="text-xs font-medium text-gray-600">Champs personnalisés du composant :</span>
|
|
<button
|
|
type="button"
|
|
@click="addComponentCustomField(component)"
|
|
class="btn btn-xs btn-outline"
|
|
>
|
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
Ajouter champ
|
|
</button>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div
|
|
v-for="(field, fieldIndex) in component.customFields"
|
|
:key="fieldIndex"
|
|
class="border border-gray-200 rounded p-2 bg-white"
|
|
>
|
|
<div class="flex items-center justify-between mb-2">
|
|
<span class="text-xs font-medium">Champ personnalisé {{ fieldIndex + 1 }}</span>
|
|
<button
|
|
type="button"
|
|
@click="removeComponentCustomField(component, fieldIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
<div>
|
|
<input
|
|
v-model="field.name"
|
|
type="text"
|
|
placeholder="Nom du champ"
|
|
class="input input-bordered input-xs w-full"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<select v-model="field.type" class="select select-bordered select-xs w-full">
|
|
<option value="">Type</option>
|
|
<option value="text">Texte</option>
|
|
<option value="number">Nombre</option>
|
|
<option value="select">Liste</option>
|
|
<option value="boolean">Oui/Non</option>
|
|
<option value="date">Date</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
|
|
<div class="flex items-center gap-2">
|
|
<input
|
|
v-model="field.required"
|
|
type="checkbox"
|
|
class="checkbox checkbox-xs"
|
|
/>
|
|
<span class="text-xs">Obligatoire</span>
|
|
</div>
|
|
<div>
|
|
<input
|
|
v-model="field.defaultValue"
|
|
type="text"
|
|
placeholder="Valeur par défaut"
|
|
class="input input-bordered input-xs w-full"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Options pour les champs de type SELECT -->
|
|
<div v-if="field.type === 'select'" class="mt-2">
|
|
<textarea
|
|
v-model="field.optionsText"
|
|
placeholder="Option 1 Option 2 Option 3"
|
|
class="textarea textarea-bordered textarea-xs w-full h-16"
|
|
@input="updateComponentFieldOptions(component, fieldIndex)"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pièces du composant principal -->
|
|
<div class="mb-3">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<span class="text-xs font-medium text-gray-600">Pièces du composant :</span>
|
|
<button
|
|
type="button"
|
|
@click="addComponentPiece(component)"
|
|
class="btn btn-xs btn-outline"
|
|
>
|
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
Ajouter pièce
|
|
</button>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div
|
|
v-for="(piece, pieceIndex) in component.pieces"
|
|
:key="pieceIndex"
|
|
class="border border-gray-200 rounded p-2 bg-white"
|
|
>
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div class="flex items-center gap-2">
|
|
<svg class="w-3 h-3 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
|
</svg>
|
|
<input
|
|
v-model="component.pieces[pieceIndex].name"
|
|
type="text"
|
|
:placeholder="`Pièce ${pieceIndex + 1}`"
|
|
class="input input-bordered input-xs flex-1"
|
|
/>
|
|
</div>
|
|
<div class="flex gap-1">
|
|
<button
|
|
type="button"
|
|
@click="addCustomFieldToPiece('component', component, pieceIndex)"
|
|
class="btn btn-xs btn-ghost"
|
|
>
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
@click="removeComponentPiece(component, pieceIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Champs personnalisés de cette pièce -->
|
|
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-2 ml-4">
|
|
<div class="space-y-1">
|
|
<div
|
|
v-for="(field, fieldIndex) in piece.customFields"
|
|
:key="fieldIndex"
|
|
class="border border-gray-100 rounded p-1 bg-gray-50"
|
|
>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
|
<button
|
|
type="button"
|
|
@click="removeCustomFieldFromPiece('component', component, fieldIndex, pieceIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1">
|
|
<input
|
|
v-model="field.name"
|
|
type="text"
|
|
placeholder="Nom"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
<select v-model="field.type" class="select select-bordered select-xs">
|
|
<option value="">Type</option>
|
|
<option value="text">Texte</option>
|
|
<option value="number">Nombre</option>
|
|
<option value="select">Liste</option>
|
|
<option value="boolean">Oui/Non</option>
|
|
<option value="date">Date</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1 mt-1">
|
|
<div class="flex items-center gap-1">
|
|
<input
|
|
v-model="field.required"
|
|
type="checkbox"
|
|
class="checkbox checkbox-xs"
|
|
/>
|
|
<span class="text-xs">Obligatoire</span>
|
|
</div>
|
|
<input
|
|
v-model="field.defaultValue"
|
|
type="text"
|
|
placeholder="Défaut"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Options pour les champs de type SELECT -->
|
|
<div v-if="field.type === 'select'" class="mt-1">
|
|
<textarea
|
|
v-model="field.optionsText"
|
|
placeholder="Option 1 Option 2 Option 3"
|
|
class="textarea textarea-bordered textarea-xs w-full h-12"
|
|
@input="updatePieceFieldOptions('component', pieceIndex, fieldIndex, component)"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-components -->
|
|
<div class="ml-6 space-y-2">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<span class="text-xs font-medium text-gray-600">Sous-composants :</span>
|
|
<button
|
|
type="button"
|
|
@click="addSubComponent(component)"
|
|
class="btn btn-xs btn-outline"
|
|
>
|
|
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
Ajouter
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
v-for="(subComponent, subIndex) in component.subComponents"
|
|
:key="subIndex"
|
|
class="border border-gray-200 rounded p-3 bg-white"
|
|
>
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<svg class="w-3 h-3 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
<input
|
|
v-model="subComponent.name"
|
|
type="text"
|
|
:placeholder="`Sous-composant ${subIndex + 1}`"
|
|
class="input input-bordered input-xs flex-1"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="removeSubComponent(component, subIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Champs personnalisés du sous-composant -->
|
|
<div class="mb-2">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="text-xs text-gray-500">Champs personnalisés :</span>
|
|
<button
|
|
type="button"
|
|
@click="addSubComponentCustomField(subComponent)"
|
|
class="btn btn-xs btn-ghost"
|
|
>
|
|
<svg class="w-2 h-2 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
+
|
|
</button>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<div
|
|
v-for="(field, fieldIndex) in subComponent.customFields"
|
|
:key="fieldIndex"
|
|
class="border border-gray-100 rounded p-1 bg-gray-50"
|
|
>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
|
<button
|
|
type="button"
|
|
@click="removeSubComponentCustomField(subComponent, fieldIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1">
|
|
<input
|
|
v-model="field.name"
|
|
type="text"
|
|
placeholder="Nom"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
<select v-model="field.type" class="select select-bordered select-xs">
|
|
<option value="">Type</option>
|
|
<option value="text">Texte</option>
|
|
<option value="number">Nombre</option>
|
|
<option value="select">Liste</option>
|
|
<option value="boolean">Oui/Non</option>
|
|
<option value="date">Date</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1 mt-1">
|
|
<div class="flex items-center gap-1">
|
|
<input
|
|
v-model="field.required"
|
|
type="checkbox"
|
|
class="checkbox checkbox-xs"
|
|
/>
|
|
<span class="text-xs">Obligatoire</span>
|
|
</div>
|
|
<input
|
|
v-model="field.defaultValue"
|
|
type="text"
|
|
placeholder="Défaut"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pièces du sous-composant -->
|
|
<div class="mb-2">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="text-xs text-gray-500">Pièces :</span>
|
|
<button
|
|
type="button"
|
|
@click="addSubComponentPiece(subComponent)"
|
|
class="btn btn-xs btn-ghost"
|
|
>
|
|
<svg class="w-2 h-2 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
+
|
|
</button>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<div
|
|
v-for="(piece, pieceIndex) in subComponent.pieces"
|
|
:key="pieceIndex"
|
|
class="border border-gray-100 rounded p-1 bg-gray-50"
|
|
>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<div class="flex items-center gap-1">
|
|
<svg class="w-2 h-2 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
|
</svg>
|
|
<input
|
|
v-model="subComponent.pieces[pieceIndex].name"
|
|
type="text"
|
|
:placeholder="`Pièce ${pieceIndex + 1}`"
|
|
class="input input-bordered input-xs flex-1"
|
|
/>
|
|
</div>
|
|
<div class="flex gap-1">
|
|
<button
|
|
type="button"
|
|
@click="addCustomFieldToPiece('subComponent', subComponent, pieceIndex)"
|
|
class="btn btn-xs btn-ghost"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
@click="removeSubComponentPiece(subComponent, pieceIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Champs personnalisés de cette pièce -->
|
|
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-1 ml-3">
|
|
<div class="space-y-1">
|
|
<div
|
|
v-for="(field, fieldIndex) in piece.customFields"
|
|
:key="fieldIndex"
|
|
class="border border-gray-50 rounded p-1 bg-gray-25"
|
|
>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
|
<button
|
|
type="button"
|
|
@click="removeCustomFieldFromPiece('subComponent', subComponent, fieldIndex, pieceIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1">
|
|
<input
|
|
v-model="field.name"
|
|
type="text"
|
|
placeholder="Nom"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
<select v-model="field.type" class="select select-bordered select-xs">
|
|
<option value="">Type</option>
|
|
<option value="text">Texte</option>
|
|
<option value="number">Nombre</option>
|
|
<option value="select">Liste</option>
|
|
<option value="boolean">Oui/Non</option>
|
|
<option value="date">Date</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1 mt-1">
|
|
<div class="flex items-center gap-1">
|
|
<input
|
|
v-model="field.required"
|
|
type="checkbox"
|
|
class="checkbox checkbox-xs"
|
|
/>
|
|
<span class="text-xs">Obligatoire</span>
|
|
</div>
|
|
<input
|
|
v-model="field.defaultValue"
|
|
type="text"
|
|
placeholder="Défaut"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Options pour les champs de type SELECT -->
|
|
<div v-if="field.type === 'select'" class="mt-1">
|
|
<textarea
|
|
v-model="field.optionsText"
|
|
placeholder="Option 1 Option 2 Option 3"
|
|
class="textarea textarea-bordered textarea-xs w-full h-10"
|
|
@input="updatePieceFieldOptions('subComponent', pieceIndex, fieldIndex, subComponent)"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-sub-components (recursive) -->
|
|
<div class="ml-4 space-y-1">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="text-xs text-gray-500">Sous-éléments :</span>
|
|
<button
|
|
type="button"
|
|
@click="addSubSubComponent(subComponent)"
|
|
class="btn btn-xs btn-ghost"
|
|
>
|
|
<svg class="w-2 h-2 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
+
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
v-for="(subSubComponent, subSubIndex) in subComponent.subComponents"
|
|
:key="subSubIndex"
|
|
class="border border-gray-100 rounded p-2 bg-gray-50"
|
|
>
|
|
<div class="flex items-center gap-2">
|
|
<svg class="w-2 h-2 text-orange-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="subSubComponent.name"
|
|
type="text"
|
|
:placeholder="`Élément ${subSubIndex + 1}`"
|
|
class="input input-bordered input-xs flex-1"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="removeSubSubComponent(subComponent, subSubIndex)"
|
|
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>
|
|
|
|
<!-- Pièces du sous-sous-composant -->
|
|
<div class="mt-1">
|
|
<div class="flex items-center gap-1 mb-1">
|
|
<span class="text-xs text-gray-400">Pièces :</span>
|
|
<button
|
|
type="button"
|
|
@click="addSubSubComponentPiece(subSubComponent)"
|
|
class="btn btn-xs btn-ghost"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="space-y-1">
|
|
<div
|
|
v-for="(piece, pieceIndex) in subSubComponent.pieces"
|
|
:key="pieceIndex"
|
|
class="border border-gray-50 rounded p-1 bg-gray-25"
|
|
>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<div class="flex items-center gap-1">
|
|
<svg class="w-2 h-2 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
|
</svg>
|
|
<input
|
|
v-model="subSubComponent.pieces[pieceIndex].name"
|
|
type="text"
|
|
:placeholder="`Pièce ${pieceIndex + 1}`"
|
|
class="input input-bordered input-xs flex-1"
|
|
/>
|
|
</div>
|
|
<div class="flex gap-1">
|
|
<button
|
|
type="button"
|
|
@click="addCustomFieldToPiece('subSubComponent', subSubComponent, pieceIndex)"
|
|
class="btn btn-xs btn-ghost"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
@click="removeSubSubComponentPiece(subSubComponent, pieceIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Champs personnalisés de cette pièce -->
|
|
<div v-if="piece.customFields && piece.customFields.length > 0" class="mt-1 ml-3">
|
|
<div class="space-y-1">
|
|
<div
|
|
v-for="(field, fieldIndex) in piece.customFields"
|
|
:key="fieldIndex"
|
|
class="border border-gray-25 rounded p-1 bg-gray-10"
|
|
>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
|
<button
|
|
type="button"
|
|
@click="removeCustomFieldFromPiece('subSubComponent', subSubComponent, fieldIndex, pieceIndex)"
|
|
class="btn btn-square btn-error btn-xs"
|
|
>
|
|
<svg class="w-2 h-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1">
|
|
<input
|
|
v-model="field.name"
|
|
type="text"
|
|
placeholder="Nom"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
<select v-model="field.type" class="select select-bordered select-xs">
|
|
<option value="">Type</option>
|
|
<option value="text">Texte</option>
|
|
<option value="number">Nombre</option>
|
|
<option value="select">Liste</option>
|
|
<option value="boolean">Oui/Non</option>
|
|
<option value="date">Date</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-1 mt-1">
|
|
<div class="flex items-center gap-1">
|
|
<input
|
|
v-model="field.required"
|
|
type="checkbox"
|
|
class="checkbox checkbox-xs"
|
|
/>
|
|
<span class="text-xs">Obligatoire</span>
|
|
</div>
|
|
<input
|
|
v-model="field.defaultValue"
|
|
type="text"
|
|
placeholder="Défaut"
|
|
class="input input-bordered input-xs"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Options pour les champs de type SELECT -->
|
|
<div v-if="field.type === 'select'" class="mt-1">
|
|
<textarea
|
|
v-model="field.optionsText"
|
|
placeholder="Option 1 Option 2 Option 3"
|
|
class="textarea textarea-bordered textarea-xs w-full h-8"
|
|
@input="updatePieceFieldOptions('subSubComponent', pieceIndex, fieldIndex, subSubComponent)"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
@click="addComponent"
|
|
class="btn btn-outline btn-sm"
|
|
>
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
Ajouter un composant
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Custom Fields -->
|
|
<div class="form-control">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm p-1"
|
|
@click="toggleSection('customFields')"
|
|
title="Plier / déplier les champs personnalisés"
|
|
>
|
|
<svg
|
|
class="w-4 h-4 transition-transform duration-200"
|
|
:class="{ 'rotate-90': sections.customFields }"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
</svg>
|
|
</button>
|
|
<label class="label m-0 flex flex-col items-start">
|
|
<span class="label-text">Champs personnalisés</span>
|
|
<span class="label-text-alt">Définissez des champs personnalisés pour ce type de machine (optionnel)</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div v-if="sections.customFields" class="space-y-4">
|
|
<div v-for="(field, index) in newType.customFields" :key="index" class="border border-gray-200 rounded-lg p-4 bg-gray-50">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<h5 class="text-sm font-medium">Champ personnalisé {{ index + 1 }}</h5>
|
|
<button
|
|
type="button"
|
|
@click="removeCustomField(index)"
|
|
class="btn btn-square btn-error btn-sm"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Nom du champ</span>
|
|
</label>
|
|
<input
|
|
v-model="field.name"
|
|
type="text"
|
|
placeholder="Ex: Température maximale"
|
|
class="input input-bordered input-sm"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Type de champ</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="form-control">
|
|
<label class="label cursor-pointer">
|
|
<span class="label-text">Champ obligatoire</span>
|
|
<input
|
|
v-model="field.required"
|
|
type="checkbox"
|
|
class="checkbox checkbox-sm"
|
|
/>
|
|
</label>
|
|
</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 (optionnel)"
|
|
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 h-20"
|
|
@input="updateFieldOptions(index)"
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
@click="addCustomField"
|
|
class="btn btn-outline btn-sm"
|
|
>
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
Ajouter un champ personnalisé
|
|
</button>
|
|
</div>
|
|
</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">
|
|
<svg 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>
|
|
Générer le type
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Generated Types List -->
|
|
<div class="mt-8">
|
|
<h3 class="text-xl font-semibold mb-4">Types générés récemment</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<div
|
|
v-for="type in recentTypes"
|
|
:key="type.id"
|
|
class="card bg-base-100 shadow-lg"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h4 class="card-title text-sm">{{ type.name }}</h4>
|
|
<div class="badge badge-primary badge-sm">{{ type.category }}</div>
|
|
</div>
|
|
<p class="text-xs text-gray-600">{{ type.description }}</p>
|
|
<div class="text-xs text-gray-500 mt-2">
|
|
{{ type.components?.length || 0 }} composants
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, computed, onMounted } from 'vue'
|
|
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
|
|
import { useToast } from '~/composables/useToast'
|
|
|
|
const { machineTypes, loading, loadMachineTypes, createMachineType } = useMachineTypesApi()
|
|
const { showSuccess } = useToast()
|
|
|
|
const categories = ref([
|
|
'Production',
|
|
'Transformation',
|
|
'Manutention',
|
|
'Traitement',
|
|
'Contrôle'
|
|
])
|
|
|
|
const recentTypes = ref([])
|
|
|
|
// Form data with hierarchical structure
|
|
const newType = reactive({
|
|
name: '',
|
|
category: '',
|
|
description: '',
|
|
maintenanceFrequency: '',
|
|
components: [], // Changed to empty array to make it optional
|
|
machinePieces: [], // Changed to empty array to make it optional
|
|
customFields: [] // Changed to empty array to make it optional
|
|
})
|
|
|
|
const sections = reactive({
|
|
machinePieces: true,
|
|
components: true,
|
|
customFields: true
|
|
})
|
|
const expandedComponents = reactive([])
|
|
|
|
const allSectionsExpanded = computed(() => sections.machinePieces && sections.components && sections.customFields)
|
|
|
|
const toggleSection = (section) => {
|
|
sections[section] = !sections[section]
|
|
}
|
|
|
|
const toggleAllSections = () => {
|
|
const next = !allSectionsExpanded.value
|
|
sections.machinePieces = next
|
|
sections.components = next
|
|
sections.customFields = next
|
|
expandedComponents.splice(0, expandedComponents.length, ...newType.components.map(() => next))
|
|
}
|
|
|
|
const isComponentExpanded = (index) => expandedComponents[index] ?? true
|
|
|
|
const toggleComponentDetails = (index) => {
|
|
expandedComponents[index] = !isComponentExpanded(index)
|
|
}
|
|
|
|
// Methods for hierarchical components
|
|
const addComponent = () => {
|
|
newType.components.push({
|
|
name: '',
|
|
subComponents: [
|
|
{
|
|
name: '',
|
|
subComponents: [],
|
|
pieces: [{ name: '', customFields: [] }],
|
|
customFields: []
|
|
}
|
|
],
|
|
pieces: [{ name: '', customFields: [] }],
|
|
customFields: []
|
|
})
|
|
expandedComponents.push(true)
|
|
}
|
|
|
|
const removeComponent = (index) => {
|
|
newType.components.splice(index, 1)
|
|
expandedComponents.splice(index, 1)
|
|
}
|
|
|
|
const addSubComponent = (component) => {
|
|
component.subComponents.push({
|
|
name: '',
|
|
subComponents: [],
|
|
pieces: [{ name: '', customFields: [] }],
|
|
customFields: []
|
|
})
|
|
}
|
|
|
|
const removeSubComponent = (component, index) => {
|
|
component.subComponents.splice(index, 1)
|
|
}
|
|
|
|
const addSubSubComponent = (subComponent) => {
|
|
subComponent.subComponents.push({
|
|
name: '',
|
|
subComponents: [],
|
|
pieces: [{ name: '', customFields: [] }],
|
|
customFields: []
|
|
})
|
|
}
|
|
|
|
const removeSubSubComponent = (subComponent, index) => {
|
|
subComponent.subComponents.splice(index, 1)
|
|
}
|
|
|
|
// Methods for components and sub-components pieces
|
|
const addComponentPiece = (component) => {
|
|
component.pieces.push({
|
|
name: '',
|
|
customFields: []
|
|
})
|
|
}
|
|
|
|
const removeComponentPiece = (component, index) => {
|
|
component.pieces.splice(index, 1)
|
|
}
|
|
|
|
const addSubComponentPiece = (subComponent) => {
|
|
subComponent.pieces.push({ name: '', customFields: [] })
|
|
}
|
|
|
|
const removeSubComponentPiece = (subComponent, index) => {
|
|
subComponent.pieces.splice(index, 1)
|
|
}
|
|
|
|
const addSubSubComponentPiece = (subSubComponent) => {
|
|
subSubComponent.pieces.push({ name: '', customFields: [] })
|
|
}
|
|
|
|
const removeSubSubComponentPiece = (subSubComponent, index) => {
|
|
subSubComponent.pieces.splice(index, 1)
|
|
}
|
|
|
|
// Methods for machine pieces
|
|
const addMachinePiece = () => {
|
|
newType.machinePieces.push({
|
|
name: '',
|
|
customFields: []
|
|
})
|
|
}
|
|
|
|
const removeMachinePiece = (index) => {
|
|
newType.machinePieces.splice(index, 1)
|
|
}
|
|
|
|
// Methods for custom fields
|
|
const addCustomField = () => {
|
|
newType.customFields.push({
|
|
name: '',
|
|
type: '',
|
|
required: false,
|
|
defaultValue: '',
|
|
options: [],
|
|
optionsText: ''
|
|
})
|
|
}
|
|
|
|
const removeCustomField = (index) => {
|
|
newType.customFields.splice(index, 1)
|
|
}
|
|
|
|
const updateFieldOptions = (index) => {
|
|
const field = newType.customFields[index]
|
|
field.options = field.optionsText.split('\n').map(option => option.trim()).filter(option => option !== '')
|
|
}
|
|
|
|
const addComponentCustomField = (component) => {
|
|
if (!component.customFields) {
|
|
component.customFields = []
|
|
}
|
|
component.customFields.push({
|
|
name: '',
|
|
type: '',
|
|
required: false,
|
|
defaultValue: '',
|
|
options: [],
|
|
optionsText: ''
|
|
})
|
|
}
|
|
|
|
const removeComponentCustomField = (component, index) => {
|
|
if (component.customFields) {
|
|
component.customFields.splice(index, 1)
|
|
}
|
|
}
|
|
|
|
const updateComponentFieldOptions = (component, index) => {
|
|
if (component.customFields && component.customFields[index]) {
|
|
const field = component.customFields[index]
|
|
field.options = field.optionsText.split('\n').map(option => option.trim()).filter(option => option !== '')
|
|
}
|
|
}
|
|
|
|
const addSubComponentCustomField = (subComponent) => {
|
|
if (!subComponent.customFields) {
|
|
subComponent.customFields = []
|
|
}
|
|
subComponent.customFields.push({
|
|
name: '',
|
|
type: '',
|
|
required: false,
|
|
defaultValue: '',
|
|
options: [],
|
|
optionsText: ''
|
|
})
|
|
}
|
|
|
|
const removeSubComponentCustomField = (subComponent, index) => {
|
|
if (subComponent.customFields) {
|
|
subComponent.customFields.splice(index, 1)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const addCustomFieldToPiece = (type, source, pieceIndex) => {
|
|
if (type === 'machine') {
|
|
if (!newType.machinePieces[source].customFields) {
|
|
newType.machinePieces[source].customFields = []
|
|
}
|
|
newType.machinePieces[source].customFields.push({
|
|
name: '',
|
|
type: '',
|
|
required: false,
|
|
defaultValue: '',
|
|
options: [],
|
|
optionsText: ''
|
|
})
|
|
} else if (type === 'component') {
|
|
if (!source.pieces[pieceIndex].customFields) {
|
|
source.pieces[pieceIndex].customFields = []
|
|
}
|
|
source.pieces[pieceIndex].customFields.push({
|
|
name: '',
|
|
type: '',
|
|
required: false,
|
|
defaultValue: '',
|
|
options: [],
|
|
optionsText: ''
|
|
})
|
|
} else if (type === 'subComponent') {
|
|
if (!source.pieces[pieceIndex].customFields) {
|
|
source.pieces[pieceIndex].customFields = []
|
|
}
|
|
source.pieces[pieceIndex].customFields.push({
|
|
name: '',
|
|
type: '',
|
|
required: false,
|
|
defaultValue: '',
|
|
options: [],
|
|
optionsText: ''
|
|
})
|
|
} else if (type === 'subSubComponent') {
|
|
if (!source.pieces[pieceIndex].customFields) {
|
|
source.pieces[pieceIndex].customFields = []
|
|
}
|
|
source.pieces[pieceIndex].customFields.push({
|
|
name: '',
|
|
type: '',
|
|
required: false,
|
|
defaultValue: '',
|
|
options: [],
|
|
optionsText: ''
|
|
})
|
|
}
|
|
}
|
|
|
|
const removeCustomFieldFromPiece = (type, source, fieldIndex, pieceIndex) => {
|
|
if (type === 'machine') {
|
|
if (newType.machinePieces[source].customFields) {
|
|
newType.machinePieces[source].customFields.splice(fieldIndex, 1)
|
|
}
|
|
} else if (type === 'component') {
|
|
if (source.pieces[pieceIndex].customFields) {
|
|
source.pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
|
}
|
|
} else if (type === 'subComponent') {
|
|
if (source.pieces[pieceIndex].customFields) {
|
|
source.pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
|
}
|
|
} else if (type === 'subSubComponent') {
|
|
if (source.pieces[pieceIndex].customFields) {
|
|
source.pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
const updatePieceFieldOptions = (type, pieceIndex, fieldIndex, source) => {
|
|
if (type === 'machine') {
|
|
const field = newType.machinePieces[source].customFields[fieldIndex]
|
|
if (field && field.optionsText) {
|
|
field.options = field.optionsText.split('\n').map(option => option.trim()).filter(option => option !== '')
|
|
}
|
|
} else if (type === 'component') {
|
|
const field = source.pieces[pieceIndex].customFields[fieldIndex]
|
|
if (field && field.optionsText) {
|
|
field.options = field.optionsText.split('\n').map(option => option.trim()).filter(option => option !== '')
|
|
}
|
|
} else if (type === 'subComponent') {
|
|
const field = source.pieces[pieceIndex].customFields[fieldIndex]
|
|
if (field && field.optionsText) {
|
|
field.options = field.optionsText.split('\n').map(option => option.trim()).filter(option => option !== '')
|
|
}
|
|
} else if (type === 'subSubComponent') {
|
|
const field = source.pieces[pieceIndex].customFields[fieldIndex]
|
|
if (field && field.optionsText) {
|
|
field.options = field.optionsText.split('\n').map(option => option.trim()).filter(option => option !== '')
|
|
}
|
|
}
|
|
}
|
|
|
|
const updateComponentPieceFieldOptions = (component, fieldIndex) => {
|
|
if (component.pieceCustomFields && component.pieceCustomFields[fieldIndex]) {
|
|
const field = component.pieceCustomFields[fieldIndex]
|
|
field.options = field.optionsText.split('\n').map(option => option.trim()).filter(option => option !== '')
|
|
}
|
|
}
|
|
|
|
const resetForm = () => {
|
|
newType.name = ''
|
|
newType.category = ''
|
|
newType.description = ''
|
|
newType.maintenanceFrequency = ''
|
|
newType.components = [] // Reset to empty array
|
|
newType.machinePieces = [] // Reset to empty array
|
|
newType.customFields = [] // Reset to empty array
|
|
expandedComponents.splice(0, expandedComponents.length)
|
|
}
|
|
|
|
const generateType = async () => {
|
|
// Clean up components (remove empty ones and clean their pieces)
|
|
const cleanComponents = newType.components
|
|
.filter(comp => comp.name.trim() !== '')
|
|
.map(comp => ({
|
|
...comp,
|
|
pieces: comp.pieces
|
|
.filter(piece => piece.name.trim() !== '')
|
|
.map(piece => ({
|
|
name: piece.name,
|
|
customFields: piece.customFields.filter(field => field.name.trim() !== '')
|
|
})),
|
|
customFields: comp.customFields.filter(field => field.name.trim() !== ''),
|
|
subComponents: comp.subComponents.map(subComp => ({
|
|
...subComp,
|
|
pieces: subComp.pieces
|
|
.filter(piece => piece.name.trim() !== '')
|
|
.map(piece => ({
|
|
name: piece.name,
|
|
customFields: piece.customFields.filter(field => field.name.trim() !== '')
|
|
})),
|
|
customFields: subComp.customFields.filter(field => field.name.trim() !== ''),
|
|
subComponents: subComp.subComponents.map(subSubComp => ({
|
|
...subSubComp,
|
|
pieces: subSubComp.pieces
|
|
.filter(piece => piece.name.trim() !== '')
|
|
.map(piece => ({
|
|
name: piece.name,
|
|
customFields: piece.customFields.filter(field => field.name.trim() !== '')
|
|
})),
|
|
customFields: subSubComp.customFields.filter(field => field.name.trim() !== '')
|
|
}))
|
|
}))
|
|
}))
|
|
|
|
// Clean up machine pieces (remove empty ones and clean custom fields)
|
|
const cleanMachinePieces = newType.machinePieces
|
|
.filter(piece => piece.name.trim() !== '')
|
|
.map(piece => ({
|
|
name: piece.name,
|
|
customFields: piece.customFields.filter(field => field.name.trim() !== '')
|
|
}))
|
|
|
|
// Clean up custom fields (remove empty ones)
|
|
const cleanCustomFields = newType.customFields.filter(field => field.name.trim() !== '')
|
|
|
|
const typeToAdd = {
|
|
name: newType.name,
|
|
category: newType.category,
|
|
description: newType.description,
|
|
maintenanceFrequency: newType.maintenanceFrequency,
|
|
components: cleanComponents,
|
|
machinePieces: cleanMachinePieces,
|
|
customFields: cleanCustomFields
|
|
}
|
|
|
|
const result = await createMachineType(typeToAdd)
|
|
|
|
if (result.success) {
|
|
// Add to recent types
|
|
recentTypes.value = machineTypes.value.slice(-3).reverse()
|
|
|
|
// Reset form
|
|
resetForm()
|
|
}
|
|
}
|
|
|
|
// Load machine types on mount
|
|
onMounted(async () => {
|
|
await loadMachineTypes()
|
|
recentTypes.value = machineTypes.value.slice(-3).reverse()
|
|
})
|
|
</script>
|