feat: reorganize machine skeleton pages
This commit is contained in:
165
app/app.vue
165
app/app.vue
@@ -125,7 +125,7 @@
|
|||||||
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
|
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
|
||||||
:class="
|
:class="
|
||||||
isActive('/component-catalog') ||
|
isActive('/component-catalog') ||
|
||||||
isActive('/component-category')
|
isActive('/component-category')
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
@@ -178,8 +178,8 @@
|
|||||||
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
|
class="rounded-md px-2 py-1 transition-colors cursor-pointer"
|
||||||
:class="
|
:class="
|
||||||
isActive('/sites') ||
|
isActive('/sites') ||
|
||||||
isActive('/documents') ||
|
isActive('/documents') ||
|
||||||
isActive('/constructeurs')
|
isActive('/constructeurs')
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
@@ -241,10 +241,7 @@
|
|||||||
<IconLucideBoxes class="w-6 h-6" aria-hidden="true" />
|
<IconLucideBoxes class="w-6 h-6" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NuxtLink
|
<NuxtLink to="/" class="btn btn-ghost text-xl">
|
||||||
to="/"
|
|
||||||
class="btn btn-ghost text-xl"
|
|
||||||
>
|
|
||||||
Inventaire Pro
|
Inventaire Pro
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -356,7 +353,7 @@
|
|||||||
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
|
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
|
||||||
:class="
|
:class="
|
||||||
isActive('/component-category') ||
|
isActive('/component-category') ||
|
||||||
isActive('/component-catalog')
|
isActive('/component-catalog')
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
@@ -409,8 +406,8 @@
|
|||||||
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
|
class="transition-colors px-3 py-2 rounded-md inline-flex items-center gap-1 cursor-pointer"
|
||||||
:class="
|
:class="
|
||||||
isActive('/sites') ||
|
isActive('/sites') ||
|
||||||
isActive('/documents') ||
|
isActive('/documents') ||
|
||||||
isActive('/constructeurs')
|
isActive('/constructeurs')
|
||||||
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
? 'bg-primary text-primary-content font-semibold shadow-sm'
|
||||||
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
: 'text-base-content hover:bg-primary/10 hover:text-primary'
|
||||||
"
|
"
|
||||||
@@ -475,40 +472,6 @@
|
|||||||
<IconLucideSettings class="w-5 h-5" aria-hidden="true" />
|
<IconLucideSettings class="w-5 h-5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Menu Nouveau -->
|
|
||||||
<div class="dropdown dropdown-end">
|
|
||||||
<div tabindex="0" role="button" class="btn btn-primary">
|
|
||||||
<IconLucidePlus class="w-5 h-5 mr-2" aria-hidden="true" />
|
|
||||||
Nouveau
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabindex="0"
|
|
||||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<NuxtLink
|
|
||||||
to="/machines?add=true"
|
|
||||||
class="flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<IconLucideCpu class="w-4 h-4" aria-hidden="true" />
|
|
||||||
Nouvelle Machine
|
|
||||||
</NuxtLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<NuxtLink to="/generator" class="flex items-center gap-2">
|
|
||||||
<IconLucideFilePlus class="w-4 h-4" aria-hidden="true" />
|
|
||||||
Nouveau Type
|
|
||||||
</NuxtLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<NuxtLink to="/sites?add=true" class="flex items-center gap-2">
|
|
||||||
<IconLucideMapPin class="w-4 h-4" aria-hidden="true" />
|
|
||||||
Nouveau Site
|
|
||||||
</NuxtLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<div v-if="activeProfile" class="dropdown dropdown-end">
|
<div v-if="activeProfile" class="dropdown dropdown-end">
|
||||||
<div
|
<div
|
||||||
@@ -531,12 +494,12 @@
|
|||||||
class="menu dropdown-content mt-3 p-2 shadow bg-base-100 rounded-box w-64"
|
class="menu dropdown-content mt-3 p-2 shadow bg-base-100 rounded-box w-64"
|
||||||
>
|
>
|
||||||
<li class="px-2 py-1 text-sm text-base-content/70">
|
<li class="px-2 py-1 text-sm text-base-content/70">
|
||||||
Connecté en tant que<br>
|
Connecté en tant que<br />
|
||||||
<span class="font-semibold text-base-content">{{
|
<span class="font-semibold text-base-content">{{
|
||||||
activeProfileLabel
|
activeProfileLabel
|
||||||
}}</span>
|
}}</span>
|
||||||
</li>
|
</li>
|
||||||
<li><hr class="my-1"></li>
|
|
||||||
<li>
|
<li>
|
||||||
<NuxtLink to="/profiles/manage" class="justify-between">
|
<NuxtLink to="/profiles/manage" class="justify-between">
|
||||||
Gestion des profils
|
Gestion des profils
|
||||||
@@ -586,99 +549,103 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||||
import { useRoute, navigateTo } from '#imports'
|
import { useRoute, navigateTo } from "#imports";
|
||||||
import { useProfileSession } from '~/composables/useProfileSession'
|
import { useProfileSession } from "~/composables/useProfileSession";
|
||||||
import IconLucideMenu from '~icons/lucide/menu'
|
import IconLucideMenu from "~icons/lucide/menu";
|
||||||
import IconLucideSettings from '~icons/lucide/settings'
|
import IconLucideSettings from "~icons/lucide/settings";
|
||||||
import IconLucideBoxes from '~icons/lucide/boxes'
|
import IconLucideBoxes from "~icons/lucide/boxes";
|
||||||
import IconLucidePlus from '~icons/lucide/plus'
|
import IconLucidePlus from "~icons/lucide/plus";
|
||||||
import IconLucideCpu from '~icons/lucide/cpu'
|
import IconLucideCpu from "~icons/lucide/cpu";
|
||||||
import IconLucideFilePlus from '~icons/lucide/file-plus'
|
import IconLucideFilePlus from "~icons/lucide/file-plus";
|
||||||
import IconLucideMapPin from '~icons/lucide/map-pin'
|
import IconLucideMapPin from "~icons/lucide/map-pin";
|
||||||
import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
import IconLucideChevronRight from "~icons/lucide/chevron-right";
|
||||||
import IconLucideLogOut from '~icons/lucide/log-out'
|
import IconLucideLogOut from "~icons/lucide/log-out";
|
||||||
|
|
||||||
// État du modal des paramètres d'affichage
|
// État du modal des paramètres d'affichage
|
||||||
const displaySettingsOpen = ref(false)
|
const displaySettingsOpen = ref(false);
|
||||||
const { activeProfile, ensureSession, logout } = useProfileSession()
|
const { activeProfile, ensureSession, logout } = useProfileSession();
|
||||||
|
|
||||||
// Route active pour souligner l'onglet sélectionné dans la navbar
|
// Route active pour souligner l'onglet sélectionné dans la navbar
|
||||||
const route = useRoute()
|
const route = useRoute();
|
||||||
const isActive = (path) => {
|
const isActive = (path) => {
|
||||||
if (path === '/') {
|
if (path === "/") {
|
||||||
return route.path === '/'
|
return route.path === "/";
|
||||||
}
|
}
|
||||||
return route.path.startsWith(path)
|
return route.path.startsWith(path);
|
||||||
}
|
};
|
||||||
|
|
||||||
// Ouvrir les paramètres d'affichage
|
// Ouvrir les paramètres d'affichage
|
||||||
const openDisplaySettings = () => {
|
const openDisplaySettings = () => {
|
||||||
displaySettingsOpen.value = true
|
displaySettingsOpen.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Fermer les paramètres d'affichage
|
// Fermer les paramètres d'affichage
|
||||||
const closeDisplaySettings = () => {
|
const closeDisplaySettings = () => {
|
||||||
displaySettingsOpen.value = false
|
displaySettingsOpen.value = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Gérer les mises à jour des paramètres
|
// Gérer les mises à jour des paramètres
|
||||||
const handleSettingsUpdate = (settings) => {
|
const handleSettingsUpdate = (settings) => {
|
||||||
console.log("Paramètres d'affichage mis à jour:", settings)
|
console.log("Paramètres d'affichage mis à jour:", settings);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await logout()
|
await logout();
|
||||||
await navigateTo('/profiles')
|
await navigateTo("/profiles");
|
||||||
}
|
};
|
||||||
|
|
||||||
const openDropdown = ref(null)
|
const openDropdown = ref(null);
|
||||||
let dropdownCloseTimer = null
|
let dropdownCloseTimer = null;
|
||||||
|
|
||||||
const setDropdown = (name) => {
|
const setDropdown = (name) => {
|
||||||
if (dropdownCloseTimer) {
|
if (dropdownCloseTimer) {
|
||||||
clearTimeout(dropdownCloseTimer)
|
clearTimeout(dropdownCloseTimer);
|
||||||
dropdownCloseTimer = null
|
dropdownCloseTimer = null;
|
||||||
}
|
}
|
||||||
if (openDropdown.value !== name) {
|
if (openDropdown.value !== name) {
|
||||||
openDropdown.value = name
|
openDropdown.value = name;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const scheduleDropdownClose = (name) => {
|
const scheduleDropdownClose = (name) => {
|
||||||
if (dropdownCloseTimer) {
|
if (dropdownCloseTimer) {
|
||||||
clearTimeout(dropdownCloseTimer)
|
clearTimeout(dropdownCloseTimer);
|
||||||
}
|
}
|
||||||
dropdownCloseTimer = setTimeout(() => {
|
dropdownCloseTimer = setTimeout(() => {
|
||||||
if (openDropdown.value === name) {
|
if (openDropdown.value === name) {
|
||||||
openDropdown.value = null
|
openDropdown.value = null;
|
||||||
}
|
}
|
||||||
dropdownCloseTimer = null
|
dropdownCloseTimer = null;
|
||||||
}, 200)
|
}, 200);
|
||||||
}
|
};
|
||||||
|
|
||||||
const activeProfileLabel = computed(() => {
|
const activeProfileLabel = computed(() => {
|
||||||
if (!activeProfile.value) { return 'Profil inconnu' }
|
if (!activeProfile.value) {
|
||||||
return `${activeProfile.value.firstName} ${activeProfile.value.lastName}`
|
return "Profil inconnu";
|
||||||
})
|
}
|
||||||
|
return `${activeProfile.value.firstName} ${activeProfile.value.lastName}`;
|
||||||
|
});
|
||||||
|
|
||||||
const activeProfileInitials = computed(() => {
|
const activeProfileInitials = computed(() => {
|
||||||
if (!activeProfile.value) { return '??' }
|
if (!activeProfile.value) {
|
||||||
const { firstName = '', lastName = '' } = activeProfile.value
|
return "??";
|
||||||
|
}
|
||||||
|
const { firstName = "", lastName = "" } = activeProfile.value;
|
||||||
return (
|
return (
|
||||||
`${firstName.charAt(0) || ''}${lastName.charAt(0) || ''}`.toUpperCase() ||
|
`${firstName.charAt(0) || ""}${lastName.charAt(0) || ""}`.toUpperCase() ||
|
||||||
'??'
|
"??"
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await ensureSession()
|
await ensureSession();
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (dropdownCloseTimer) {
|
if (dropdownCloseTimer) {
|
||||||
clearTimeout(dropdownCloseTimer)
|
clearTimeout(dropdownCloseTimer);
|
||||||
dropdownCloseTimer = null
|
dropdownCloseTimer = null;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -70,43 +70,29 @@
|
|||||||
>
|
>
|
||||||
<div class="flex items-start justify-between gap-2">
|
<div class="flex items-start justify-between gap-2">
|
||||||
<div class="flex-1 space-y-3">
|
<div class="flex-1 space-y-3">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div class="form-control">
|
||||||
<div class="form-control">
|
<label class="label"><span class="label-text">Famille de pièce</span></label>
|
||||||
<label class="label"><span class="label-text">Famille de pièce</span></label>
|
<div>
|
||||||
<div>
|
<select
|
||||||
<input
|
v-model="piece.typePieceId"
|
||||||
:list="`component-piece-type-options-${index}`"
|
class="select select-bordered select-xs"
|
||||||
v-model="piece.typePieceLabel"
|
@change="handlePieceTypeSelect(piece)"
|
||||||
type="search"
|
>
|
||||||
autocomplete="off"
|
<option value="">
|
||||||
class="input input-bordered input-xs"
|
Sélectionner une famille
|
||||||
placeholder="Sélectionner une famille"
|
</option>
|
||||||
@change="handlePieceTypeChange(piece)"
|
<option
|
||||||
@blur="handlePieceTypeChange(piece)"
|
v-for="type in availablePieceTypes"
|
||||||
/>
|
:key="type.id"
|
||||||
<datalist :id="`component-piece-type-options-${index}`">
|
:value="type.id"
|
||||||
<option
|
>
|
||||||
v-for="type in availablePieceTypes"
|
{{ formatPieceTypeOption(type) }}
|
||||||
:key="type.id"
|
</option>
|
||||||
:value="formatPieceTypeOption(type)"
|
</select>
|
||||||
/>
|
|
||||||
</datalist>
|
|
||||||
</div>
|
|
||||||
<p class="mt-1 text-[11px] text-gray-500">
|
|
||||||
{{ piece.typePieceId ? `Sélection : ${getPieceTypeLabel(piece.typePieceId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label"><span class="label-text">Quantité (optionnel)</span></label>
|
|
||||||
<input
|
|
||||||
v-model.number="piece.quantity"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
class="input input-bordered input-xs"
|
|
||||||
placeholder="Quantité"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="mt-1 text-[11px] text-gray-500">
|
||||||
|
{{ piece.typePieceId ? `Sélection : ${getPieceTypeLabel(piece.typePieceId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-error btn-xs btn-square" @click="removePiece(index)">
|
<button type="button" class="btn btn-error btn-xs btn-square" @click="removePiece(index)">
|
||||||
@@ -134,7 +120,6 @@
|
|||||||
:key="`root-sub-${index}`"
|
:key="`root-sub-${index}`"
|
||||||
:node="subComponent"
|
:node="subComponent"
|
||||||
:depth="0"
|
:depth="0"
|
||||||
:piece-types="availablePieceTypes"
|
|
||||||
:component-types="availableComponentTypes"
|
:component-types="availableComponentTypes"
|
||||||
@remove="removeSubComponent(index)"
|
@remove="removeSubComponent(index)"
|
||||||
/>
|
/>
|
||||||
@@ -151,7 +136,7 @@ import StructureSubComponentEditor from '~/components/StructureSubComponentEdito
|
|||||||
import {
|
import {
|
||||||
defaultStructure,
|
defaultStructure,
|
||||||
hydrateStructureForEditor,
|
hydrateStructureForEditor,
|
||||||
normalizeStructureForSave,
|
cloneStructure,
|
||||||
} from '~/shared/modelUtils'
|
} from '~/shared/modelUtils'
|
||||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||||
@@ -174,7 +159,7 @@ const syncFromProps = (value: any) => {
|
|||||||
localStructure.customFields = hydrated.customFields
|
localStructure.customFields = hydrated.customFields
|
||||||
localStructure.pieces = hydrated.pieces
|
localStructure.pieces = hydrated.pieces
|
||||||
localStructure.subComponents = hydrated.subComponents
|
localStructure.subComponents = hydrated.subComponents
|
||||||
lastEmitted = JSON.stringify(normalizeStructureForSave(value))
|
lastEmitted = JSON.stringify(cloneStructure(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -185,16 +170,16 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
let lastEmitted = JSON.stringify(normalizeStructureForSave(props.modelValue))
|
let lastEmitted = JSON.stringify(cloneStructure(props.modelValue))
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
localStructure,
|
localStructure,
|
||||||
(value) => {
|
(value) => {
|
||||||
const normalized = normalizeStructureForSave(value)
|
const payload = cloneStructure(value)
|
||||||
const serialized = JSON.stringify(normalized)
|
const serialized = JSON.stringify(payload)
|
||||||
if (serialized !== lastEmitted) {
|
if (serialized !== lastEmitted) {
|
||||||
lastEmitted = serialized
|
lastEmitted = serialized
|
||||||
emit('update:modelValue', normalized)
|
emit('update:modelValue', payload)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
@@ -302,34 +287,35 @@ const updatePieceTypeLabel = (piece: any) => {
|
|||||||
piece.typePieceId = match.id
|
piece.typePieceId = match.id
|
||||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
piece.typePieceLabel = formatPieceTypeOption(match)
|
||||||
piece.name = match.name || formatPieceTypeOption(match)
|
piece.name = match.name || formatPieceTypeOption(match)
|
||||||
} else {
|
} else if (!piece.name) {
|
||||||
piece.typePieceLabel = ''
|
piece.name = piece.typePieceLabel
|
||||||
piece.name = ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePieceTypeChange = (piece: any) => {
|
const handlePieceTypeSelect = (piece: any) => {
|
||||||
if (!piece) {
|
if (!piece) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const value = typeof piece.typePieceLabel === 'string' ? piece.typePieceLabel.trim() : ''
|
const id = typeof piece.typePieceId === 'string' ? piece.typePieceId : ''
|
||||||
if (!value) {
|
|
||||||
|
if (!id) {
|
||||||
piece.typePieceId = ''
|
piece.typePieceId = ''
|
||||||
piece.typePieceLabel = ''
|
piece.typePieceLabel = ''
|
||||||
piece.name = ''
|
piece.name = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const match = resolvePieceType(value)
|
|
||||||
if (match) {
|
const option = pieceTypeMap.value.get(id)
|
||||||
piece.typePieceId = match.id
|
if (!option) {
|
||||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
|
||||||
piece.name = match.name || formatPieceTypeOption(match)
|
|
||||||
} else {
|
|
||||||
piece.typePieceId = ''
|
piece.typePieceId = ''
|
||||||
piece.typePieceLabel = ''
|
piece.typePieceLabel = ''
|
||||||
piece.name = ''
|
piece.name = ''
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
piece.typePieceLabel = formatPieceTypeOption(option)
|
||||||
|
piece.name = option.name || piece.typePieceLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyPieceLabels = (pieces?: any[]) => {
|
const applyPieceLabels = (pieces?: any[]) => {
|
||||||
@@ -345,9 +331,8 @@ const applyPieceLabels = (pieces?: any[]) => {
|
|||||||
piece.typePieceId = match.id
|
piece.typePieceId = match.id
|
||||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
piece.typePieceLabel = formatPieceTypeOption(match)
|
||||||
piece.name = match.name || formatPieceTypeOption(match)
|
piece.name = match.name || formatPieceTypeOption(match)
|
||||||
} else {
|
} else if (!piece.name) {
|
||||||
piece.typePieceLabel = ''
|
piece.name = piece.typePieceLabel
|
||||||
piece.name = ''
|
|
||||||
}
|
}
|
||||||
} else if (!piece?.name) {
|
} else if (!piece?.name) {
|
||||||
piece.name = ''
|
piece.name = ''
|
||||||
@@ -452,7 +437,6 @@ const addPiece = () => {
|
|||||||
ensureArray('pieces')
|
ensureArray('pieces')
|
||||||
localStructure.pieces.push({
|
localStructure.pieces.push({
|
||||||
name: '',
|
name: '',
|
||||||
quantity: undefined,
|
|
||||||
typePieceId: '',
|
typePieceId: '',
|
||||||
typePieceLabel: '',
|
typePieceLabel: '',
|
||||||
})
|
})
|
||||||
@@ -467,13 +451,8 @@ const addSubComponent = () => {
|
|||||||
ensureArray('subComponents')
|
ensureArray('subComponents')
|
||||||
localStructure.subComponents.push({
|
localStructure.subComponents.push({
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
|
||||||
quantity: undefined,
|
|
||||||
typeComposantId: '',
|
typeComposantId: '',
|
||||||
typeComposantLabel: '',
|
typeComposantLabel: '',
|
||||||
customFields: [],
|
|
||||||
pieces: [],
|
|
||||||
subComponents: [],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,213 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="border border-base-200 rounded-lg bg-base-100" :class="depthPadding">
|
<div class="border border-base-200 rounded-lg bg-base-100" :class="depthPadding">
|
||||||
<div class="flex items-start justify-between gap-3 border-b border-base-200 px-4 py-3">
|
<div class="flex items-center justify-between gap-3 px-4 py-3">
|
||||||
<div class="flex flex-col gap-1 flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2">
|
<select
|
||||||
<button type="button" class="btn btn-ghost btn-xs" @click="toggle">
|
v-model="node.typeComposantId"
|
||||||
<IconLucideChevronRight
|
class="select select-bordered select-sm w-full"
|
||||||
class="w-4 h-4 transition-transform"
|
@change="handleComponentTypeSelect(node)"
|
||||||
:class="{ 'rotate-90': expanded }"
|
>
|
||||||
aria-hidden="true"
|
<option value="">
|
||||||
/>
|
Sélectionner une famille de composant
|
||||||
</button>
|
</option>
|
||||||
<div class="flex-1">
|
<option
|
||||||
<input
|
v-for="type in componentTypes"
|
||||||
:list="componentTypeListId"
|
:key="type.id"
|
||||||
v-model="node.typeComposantLabel"
|
:value="type.id"
|
||||||
type="search"
|
>
|
||||||
autocomplete="off"
|
{{ formatComponentTypeOption(type) }}
|
||||||
class="input input-sm input-bordered w-full"
|
</option>
|
||||||
placeholder="Sélectionner une famille de composant"
|
</select>
|
||||||
@change="handleComponentTypeChange(node)"
|
<p class="mt-1 text-[11px] text-gray-500">
|
||||||
@blur="handleComponentTypeChange(node)"
|
{{ node.typeComposantId ? `Sélection : ${getComponentTypeLabel(node.typeComposantId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
||||||
/>
|
</p>
|
||||||
<datalist :id="componentTypeListId">
|
|
||||||
<option
|
|
||||||
v-for="type in componentTypes"
|
|
||||||
:key="type.id"
|
|
||||||
:value="formatComponentTypeOption(type)"
|
|
||||||
/>
|
|
||||||
</datalist>
|
|
||||||
<p class="mt-1 text-[11px] text-gray-500">
|
|
||||||
{{ node.typeComposantId ? `Sélection : ${getComponentTypeLabel(node.typeComposantId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span v-if="!expanded && node.description" class="text-xs text-gray-500 truncate">
|
|
||||||
{{ node.description }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-error btn-xs btn-square" @click="emit('remove')">
|
<button type="button" class="btn btn-error btn-xs btn-square" @click="emit('remove')">
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="expanded" class="space-y-5 px-4 py-4">
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label"><span class="label-text">Description</span></label>
|
|
||||||
<textarea
|
|
||||||
v-model="node.description"
|
|
||||||
class="textarea textarea-bordered textarea-sm"
|
|
||||||
rows="2"
|
|
||||||
placeholder="Notes optionnelles"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label"><span class="label-text">Quantité</span></label>
|
|
||||||
<input
|
|
||||||
v-model.number="node.quantity"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
class="input input-bordered input-sm"
|
|
||||||
placeholder="Quantité (optionnel)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="space-y-2">
|
|
||||||
<h4 class="text-sm font-semibold">Champs personnalisés</h4>
|
|
||||||
<p v-if="!(node.customFields?.length)" class="text-xs text-gray-500">
|
|
||||||
Aucun champ défini.
|
|
||||||
</p>
|
|
||||||
<div v-else class="space-y-2">
|
|
||||||
<div
|
|
||||||
v-for="(field, fieldIndex) in node.customFields"
|
|
||||||
:key="`sub-${depth}-${fieldIndex}`"
|
|
||||||
class="border border-base-200 rounded-md p-3 space-y-2"
|
|
||||||
>
|
|
||||||
<div class="flex items-start justify-between gap-2">
|
|
||||||
<div class="flex-1 space-y-2">
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
||||||
<input
|
|
||||||
v-model="field.name"
|
|
||||||
type="text"
|
|
||||||
class="input input-bordered input-xs"
|
|
||||||
placeholder="Nom du champ"
|
|
||||||
/>
|
|
||||||
<select v-model="field.type" class="select select-bordered select-xs">
|
|
||||||
<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="flex items-center gap-2 text-xs">
|
|
||||||
<input v-model="field.required" type="checkbox" class="checkbox checkbox-xs" />
|
|
||||||
Obligatoire
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
v-if="field.type === 'select'"
|
|
||||||
v-model="field.optionsText"
|
|
||||||
class="textarea textarea-bordered textarea-xs h-20"
|
|
||||||
placeholder="Option 1 Option 2"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-error btn-xs btn-square" @click="removeCustomField(fieldIndex)">
|
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-outline btn-xs" @click="addCustomField">
|
|
||||||
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
|
|
||||||
Ajouter un champ
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="space-y-2">
|
|
||||||
<h4 class="text-sm font-semibold">Pièces associées</h4>
|
|
||||||
<p v-if="!(node.pieces?.length)" class="text-xs text-gray-500">Aucune pièce définie.</p>
|
|
||||||
<div v-else class="space-y-2">
|
|
||||||
<div
|
|
||||||
v-for="(piece, pieceIndex) in node.pieces"
|
|
||||||
:key="`piece-${depth}-${pieceIndex}`"
|
|
||||||
class="border border-base-200 rounded-md p-3 space-y-3"
|
|
||||||
>
|
|
||||||
<div class="flex items-start justify-between gap-2">
|
|
||||||
<div class="flex-1 space-y-3">
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label"><span class="label-text">Famille de pièce</span></label>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
:list="getPieceTypeListId(pieceIndex)"
|
|
||||||
v-model="piece.typePieceLabel"
|
|
||||||
type="search"
|
|
||||||
autocomplete="off"
|
|
||||||
class="input input-bordered input-xs"
|
|
||||||
placeholder="Sélectionner une famille"
|
|
||||||
@change="handlePieceTypeChange(piece)"
|
|
||||||
@blur="handlePieceTypeChange(piece)"
|
|
||||||
/>
|
|
||||||
<datalist :id="getPieceTypeListId(pieceIndex)">
|
|
||||||
<option
|
|
||||||
v-for="type in pieceTypes"
|
|
||||||
:key="type.id"
|
|
||||||
:value="formatPieceTypeOption(type)"
|
|
||||||
/>
|
|
||||||
</datalist>
|
|
||||||
</div>
|
|
||||||
<p class="mt-1 text-[11px] text-gray-500">
|
|
||||||
{{ piece.typePieceId ? `Sélection : ${getPieceTypeLabel(piece.typePieceId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label"><span class="label-text">Quantité (optionnel)</span></label>
|
|
||||||
<input
|
|
||||||
v-model.number="piece.quantity"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
class="input input-bordered input-xs"
|
|
||||||
placeholder="Quantité"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-error btn-xs btn-square" @click="removePiece(pieceIndex)">
|
|
||||||
<IconLucideTrash class="w-4 h-4" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-outline btn-xs" @click="addPiece">
|
|
||||||
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
|
|
||||||
Ajouter une pièce
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="space-y-3">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h4 class="text-sm font-semibold">Sous-composants</h4>
|
|
||||||
<button type="button" class="btn btn-outline btn-xs" @click="addSubComponent">
|
|
||||||
<IconLucidePlus class="w-3 h-3 mr-2" aria-hidden="true" />
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p v-if="!(node.subComponents?.length)" class="text-xs text-gray-500">Aucun sous-composant défini.</p>
|
|
||||||
<div v-else class="space-y-3">
|
|
||||||
<StructureSubComponentEditor
|
|
||||||
v-for="(sub, index) in node.subComponents"
|
|
||||||
:key="`sub-${depth}-${index}`"
|
|
||||||
:node="sub"
|
|
||||||
:depth="depth + 1"
|
|
||||||
:piece-types="pieceTypes"
|
|
||||||
:component-types="componentTypes"
|
|
||||||
@remove="removeSubComponent(index)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch, getCurrentInstance } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
|
||||||
import IconLucidePlus from '~icons/lucide/plus'
|
|
||||||
import IconLucideTrash from '~icons/lucide/trash'
|
import IconLucideTrash from '~icons/lucide/trash'
|
||||||
|
|
||||||
defineOptions({ name: 'StructureSubComponentEditor' })
|
defineOptions({ name: 'StructureSubComponentEditor' })
|
||||||
@@ -221,36 +44,30 @@ type ModelTypeOption = {
|
|||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
node: Record<string, any>
|
node: Record<string, any>
|
||||||
depth?: number
|
depth?: number
|
||||||
pieceTypes?: ModelTypeOption[]
|
|
||||||
componentTypes?: ModelTypeOption[]
|
componentTypes?: ModelTypeOption[]
|
||||||
}>(), {
|
}>(), {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
pieceTypes: () => [],
|
|
||||||
componentTypes: () => [],
|
componentTypes: () => [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['remove'])
|
const emit = defineEmits(['remove'])
|
||||||
|
|
||||||
const pieceTypes = computed(() => props.pieceTypes ?? [])
|
|
||||||
const componentTypes = computed(() => props.componentTypes ?? [])
|
const componentTypes = computed(() => props.componentTypes ?? [])
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
const depthPadding = computed(() => {
|
||||||
const componentTypeListId = `sub-component-type-options-${instance?.uid ?? 0}`
|
const level = props.depth ?? 0
|
||||||
|
return level > 0 ? `ml-${Math.min(level * 4, 12)}` : ''
|
||||||
|
})
|
||||||
|
|
||||||
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => {
|
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => {
|
||||||
if (!type) return ''
|
if (!type) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
return type.code ? `${type.name} (${type.code})` : type.name
|
return type.code ? `${type.name} (${type.code})` : type.name
|
||||||
}
|
}
|
||||||
|
|
||||||
const pieceTypeMap = computed(() => {
|
const formatComponentTypeOption = (type: ModelTypeOption | undefined | null) =>
|
||||||
const map = new Map<string, ModelTypeOption>()
|
formatModelTypeOption(type)
|
||||||
pieceTypes.value.forEach((type) => {
|
|
||||||
if (type && typeof type.id === 'string') {
|
|
||||||
map.set(type.id, type)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
const componentTypeMap = computed(() => {
|
const componentTypeMap = computed(() => {
|
||||||
const map = new Map<string, ModelTypeOption>()
|
const map = new Map<string, ModelTypeOption>()
|
||||||
@@ -262,275 +79,51 @@ const componentTypeMap = computed(() => {
|
|||||||
return map
|
return map
|
||||||
})
|
})
|
||||||
|
|
||||||
const formatPieceTypeOption = (type: ModelTypeOption | undefined | null) => formatModelTypeOption(type)
|
|
||||||
const formatComponentTypeOption = (type: ModelTypeOption | undefined | null) => formatModelTypeOption(type)
|
|
||||||
|
|
||||||
const resolvePieceType = (input: string) => {
|
|
||||||
const normalized = input.trim().toLowerCase()
|
|
||||||
if (!normalized) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
pieceTypes.value.find((type) => {
|
|
||||||
const formatted = formatPieceTypeOption(type).toLowerCase()
|
|
||||||
const name = (type?.name ?? '').toLowerCase()
|
|
||||||
const code = (type?.code ?? '').toLowerCase()
|
|
||||||
return (
|
|
||||||
formatted === normalized
|
|
||||||
|| name === normalized
|
|
||||||
|| (!!code && code === normalized)
|
|
||||||
)
|
|
||||||
}) ?? null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolveComponentType = (input: string) => {
|
|
||||||
const normalized = input.trim().toLowerCase()
|
|
||||||
if (!normalized) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
componentTypes.value.find((type) => {
|
|
||||||
const formatted = formatComponentTypeOption(type).toLowerCase()
|
|
||||||
const name = (type?.name ?? '').toLowerCase()
|
|
||||||
const code = (type?.code ?? '').toLowerCase()
|
|
||||||
return (
|
|
||||||
formatted === normalized
|
|
||||||
|| name === normalized
|
|
||||||
|| (!!code && code === normalized)
|
|
||||||
)
|
|
||||||
}) ?? null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPieceTypeLabel = (id?: string) => {
|
|
||||||
if (!id) return ''
|
|
||||||
const option = pieceTypeMap.value.get(id)
|
|
||||||
return formatPieceTypeOption(option)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getComponentTypeLabel = (id?: string) => {
|
const getComponentTypeLabel = (id?: string) => {
|
||||||
if (!id) return ''
|
if (!id) return ''
|
||||||
const option = componentTypeMap.value.get(id)
|
return formatModelTypeOption(componentTypeMap.value.get(id))
|
||||||
return formatComponentTypeOption(option)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePieceTypeLabel = (piece: any) => {
|
const syncComponentType = (component: any) => {
|
||||||
if (!piece) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (piece.typePieceId) {
|
|
||||||
const option = pieceTypeMap.value.get(piece.typePieceId)
|
|
||||||
if (option) {
|
|
||||||
piece.typePieceLabel = formatPieceTypeOption(option)
|
|
||||||
piece.name = option.name || formatPieceTypeOption(option)
|
|
||||||
} else if (!piece.typePieceLabel) {
|
|
||||||
piece.name = ''
|
|
||||||
}
|
|
||||||
} else if (piece.typePieceLabel) {
|
|
||||||
const match = resolvePieceType(piece.typePieceLabel)
|
|
||||||
if (match) {
|
|
||||||
piece.typePieceId = match.id
|
|
||||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
|
||||||
piece.name = match.name || formatPieceTypeOption(match)
|
|
||||||
} else {
|
|
||||||
piece.typePieceLabel = ''
|
|
||||||
piece.name = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePieceTypeChange = (piece: any) => {
|
|
||||||
if (!piece) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const value = typeof piece.typePieceLabel === 'string' ? piece.typePieceLabel.trim() : ''
|
|
||||||
if (!value) {
|
|
||||||
piece.typePieceId = ''
|
|
||||||
piece.typePieceLabel = ''
|
|
||||||
piece.name = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const match = resolvePieceType(value)
|
|
||||||
if (match) {
|
|
||||||
piece.typePieceId = match.id
|
|
||||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
|
||||||
piece.name = match.name || formatPieceTypeOption(match)
|
|
||||||
} else {
|
|
||||||
piece.typePieceId = ''
|
|
||||||
piece.typePieceLabel = ''
|
|
||||||
piece.name = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPieceTypeListId = (pieceIndex: number) => `sub-piece-type-options-${props.depth ?? 0}-${pieceIndex}`
|
|
||||||
|
|
||||||
const applyPieceLabels = (pieces?: any[]) => {
|
|
||||||
if (!Array.isArray(pieces)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pieces.forEach((piece) => {
|
|
||||||
if (piece?.typePieceId) {
|
|
||||||
updatePieceTypeLabel(piece)
|
|
||||||
} else if (piece?.typePieceLabel) {
|
|
||||||
const match = resolvePieceType(piece.typePieceLabel)
|
|
||||||
if (match) {
|
|
||||||
piece.typePieceId = match.id
|
|
||||||
piece.typePieceLabel = formatPieceTypeOption(match)
|
|
||||||
piece.name = match.name || formatPieceTypeOption(match)
|
|
||||||
} else {
|
|
||||||
piece.typePieceLabel = ''
|
|
||||||
piece.name = ''
|
|
||||||
}
|
|
||||||
} else if (!piece?.name) {
|
|
||||||
piece.name = ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyComponentTypeLabel = (component: any) => {
|
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (component.typeComposantId) {
|
const id = typeof component.typeComposantId === 'string'
|
||||||
const option = componentTypeMap.value.get(component.typeComposantId)
|
? component.typeComposantId
|
||||||
if (option) {
|
|
||||||
component.typeComposantLabel = formatComponentTypeOption(option)
|
|
||||||
component.name = option.name || formatComponentTypeOption(option)
|
|
||||||
} else if (!component.typeComposantLabel) {
|
|
||||||
component.name = ''
|
|
||||||
}
|
|
||||||
} else if (component.typeComposantLabel) {
|
|
||||||
const match = resolveComponentType(component.typeComposantLabel)
|
|
||||||
if (match) {
|
|
||||||
component.typeComposantId = match.id
|
|
||||||
component.typeComposantLabel = formatComponentTypeOption(match)
|
|
||||||
component.name = match.name || formatComponentTypeOption(match)
|
|
||||||
} else {
|
|
||||||
component.typeComposantLabel = ''
|
|
||||||
component.name = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleComponentTypeChange = (component: any) => {
|
|
||||||
if (!component) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const value = typeof component.typeComposantLabel === 'string'
|
|
||||||
? component.typeComposantLabel.trim()
|
|
||||||
: ''
|
: ''
|
||||||
if (!value) {
|
|
||||||
component.typeComposantId = ''
|
if (!id) {
|
||||||
component.typeComposantLabel = ''
|
component.typeComposantLabel = ''
|
||||||
component.name = ''
|
component.name = ''
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const match = resolveComponentType(value)
|
|
||||||
if (match) {
|
const option = componentTypeMap.value.get(id)
|
||||||
component.typeComposantId = match.id
|
if (!option) {
|
||||||
component.typeComposantLabel = formatComponentTypeOption(match)
|
|
||||||
component.name = match.name || formatComponentTypeOption(match)
|
|
||||||
} else {
|
|
||||||
component.typeComposantId = ''
|
|
||||||
component.typeComposantLabel = ''
|
component.typeComposantLabel = ''
|
||||||
component.name = ''
|
component.name = ''
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const traverseSubComponents = (components?: any[]) => {
|
|
||||||
if (!Array.isArray(components)) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
components.forEach((component) => {
|
|
||||||
applyComponentTypeLabel(component)
|
component.typeComposantLabel = formatModelTypeOption(option)
|
||||||
applyPieceLabels(component?.pieces)
|
component.name = option.name || component.typeComposantLabel
|
||||||
traverseSubComponents(component?.subComponents)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncTypeLabels = () => {
|
const handleComponentTypeSelect = (component: any) => {
|
||||||
applyComponentTypeLabel(props.node)
|
syncComponentType(component)
|
||||||
applyPieceLabels(props.node?.pieces)
|
|
||||||
traverseSubComponents(props.node?.subComponents)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(pieceTypes, () => {
|
|
||||||
syncTypeLabels()
|
|
||||||
}, { deep: true, immediate: true })
|
|
||||||
|
|
||||||
watch(componentTypes, () => {
|
watch(componentTypes, () => {
|
||||||
syncTypeLabels()
|
syncComponentType(props.node)
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.node,
|
() => props.node.typeComposantId,
|
||||||
() => {
|
() => {
|
||||||
syncTypeLabels()
|
syncComponentType(props.node)
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const expanded = ref(true)
|
|
||||||
const depthPadding = computed(() => (props.depth > 0 ? 'ml-4' : ''))
|
|
||||||
|
|
||||||
const toggle = () => {
|
|
||||||
expanded.value = !expanded.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const ensureArray = (key: 'customFields' | 'pieces' | 'subComponents') => {
|
|
||||||
if (!Array.isArray(props.node[key])) {
|
|
||||||
props.node[key] = []
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
const addCustomField = () => {
|
|
||||||
ensureArray('customFields')
|
|
||||||
props.node.customFields.push({
|
|
||||||
name: '',
|
|
||||||
type: 'text',
|
|
||||||
required: false,
|
|
||||||
optionsText: '',
|
|
||||||
options: [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeCustomField = (index: number) => {
|
|
||||||
if (!Array.isArray(props.node.customFields)) return
|
|
||||||
props.node.customFields.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addPiece = () => {
|
|
||||||
ensureArray('pieces')
|
|
||||||
props.node.pieces.push({
|
|
||||||
name: '',
|
|
||||||
quantity: undefined,
|
|
||||||
typePieceId: '',
|
|
||||||
typePieceLabel: '',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const removePiece = (index: number) => {
|
|
||||||
if (!Array.isArray(props.node.pieces)) return
|
|
||||||
props.node.pieces.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addSubComponent = () => {
|
|
||||||
ensureArray('subComponents')
|
|
||||||
props.node.subComponents.push({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
quantity: undefined,
|
|
||||||
typeComposantId: '',
|
|
||||||
typeComposantLabel: '',
|
|
||||||
customFields: [],
|
|
||||||
pieces: [],
|
|
||||||
subComponents: [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeSubComponent = (index: number) => {
|
|
||||||
if (!Array.isArray(props.node.subComponents)) return
|
|
||||||
props.node.subComponents.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,739 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-outline btn-sm"
|
|
||||||
@click="toggleAllComponents"
|
|
||||||
>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-4 h-4 mr-2"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<h5 class="text-sm font-medium">
|
|
||||||
Nouveau composant {{ index + 1 }}
|
|
||||||
</h5>
|
|
||||||
<span v-if="!isComponentExpanded(index)" class="text-xs text-gray-500 truncate max-w-[160px]">
|
|
||||||
{{ component.name || 'Sans nom' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-square btn-error btn-sm"
|
|
||||||
@click="removeComponent(index)"
|
|
||||||
>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-4 h-4"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<span class="text-xs font-medium">Champ personnalisé {{ fieldIndex + 1 }}</span>
|
|
||||||
<span v-if="!isComponentCustomFieldExpanded(index, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[120px]">
|
|
||||||
{{ field.name || 'Sans nom' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-square btn-error btn-xs"
|
|
||||||
@click="removeComponentCustomField(index, fieldIndex)"
|
|
||||||
>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-3 h-3"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-3 h-3 text-red-500"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
|
||||||
<span v-if="!isComponentPieceCustomFieldExpanded(index, pieceIndex, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[100px]">
|
|
||||||
{{ field.name || 'Sans nom' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-square btn-error btn-xs"
|
|
||||||
@click="removeComponentPieceCustomField(index, pieceIndex, fieldIndex)"
|
|
||||||
>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-2 h-2"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-3 h-3 text-green-500"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
|
||||||
<span v-if="!isSubComponentCustomFieldExpanded(index, subIndex, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[100px]">
|
|
||||||
{{ field.name || 'Sans nom' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-square btn-error btn-xs"
|
|
||||||
@click="removeSubComponentCustomField(index, subIndex, fieldIndex)"
|
|
||||||
>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-2 h-2"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<IconLucideChevronRight
|
|
||||||
class="w-2 h-2 text-red-500"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<span class="text-xs">Champ {{ fieldIndex + 1 }}</span>
|
|
||||||
<span v-if="!isSubComponentPieceCustomFieldExpanded(index, subIndex, pieceIndex, fieldIndex)" class="text-[10px] text-gray-500 truncate max-w-[100px]">
|
|
||||||
{{ field.name || 'Sans nom' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-square btn-error btn-xs"
|
|
||||||
@click="removeSubComponentPieceCustomField(index, subIndex, pieceIndex, fieldIndex)"
|
|
||||||
>
|
|
||||||
<IconLucideX
|
|
||||||
class="w-2 h-2"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isSubComponentPieceCustomFieldExpanded(index, subIndex, pieceIndex, fieldIndex)" 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 v-if="isSubComponentPieceCustomFieldExpanded(index, subIndex, pieceIndex, fieldIndex)" class="mt-1">
|
|
||||||
<label class="flex items-center gap-1 text-xs">
|
|
||||||
<input
|
|
||||||
v-model="field.required"
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-xs"
|
|
||||||
>
|
|
||||||
Obligatoire
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isSubComponentPieceCustomFieldExpanded(index, subIndex, pieceIndex, fieldIndex) && 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="updateSubComponentPieceFieldOptions(index, subIndex, pieceIndex, fieldIndex)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-outline btn-sm"
|
|
||||||
@click="addComponent"
|
|
||||||
>
|
|
||||||
<IconLucidePlus
|
|
||||||
class="w-4 h-4 mr-2"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Ajouter un composant
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive, onMounted, watch } from 'vue'
|
|
||||||
import IconLucideChevronRight from '~icons/lucide/chevron-right'
|
|
||||||
import IconLucideX from '~icons/lucide/x'
|
|
||||||
import IconLucidePlus from '~icons/lucide/plus'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const components = ref(props.modelValue)
|
|
||||||
|
|
||||||
watch(() => props.modelValue, (newValue) => {
|
|
||||||
components.value = newValue
|
|
||||||
initializeExpansionState()
|
|
||||||
})
|
|
||||||
|
|
||||||
const allExpanded = ref(false)
|
|
||||||
const expandedComponents = ref([])
|
|
||||||
const expandedComponentCustomFields = reactive({})
|
|
||||||
const expandedComponentPieces = reactive({})
|
|
||||||
const expandedComponentPieceCustomFields = reactive({})
|
|
||||||
const expandedSubComponents = reactive({})
|
|
||||||
const expandedSubComponentCustomFields = reactive({})
|
|
||||||
const expandedSubComponentPieces = reactive({})
|
|
||||||
const expandedSubComponentPieceCustomFields = reactive({})
|
|
||||||
|
|
||||||
const ensureComponentState = (componentIndex) => {
|
|
||||||
if (!expandedComponentCustomFields[componentIndex]) {
|
|
||||||
expandedComponentCustomFields[componentIndex] = []
|
|
||||||
}
|
|
||||||
if (!expandedComponentPieces[componentIndex]) {
|
|
||||||
expandedComponentPieces[componentIndex] = []
|
|
||||||
}
|
|
||||||
if (!expandedComponentPieceCustomFields[componentIndex]) {
|
|
||||||
expandedComponentPieceCustomFields[componentIndex] = {}
|
|
||||||
}
|
|
||||||
if (!expandedSubComponents[componentIndex]) {
|
|
||||||
expandedSubComponents[componentIndex] = []
|
|
||||||
}
|
|
||||||
if (!expandedSubComponentCustomFields[componentIndex]) {
|
|
||||||
expandedSubComponentCustomFields[componentIndex] = {}
|
|
||||||
}
|
|
||||||
if (!expandedSubComponentPieces[componentIndex]) {
|
|
||||||
expandedSubComponentPieces[componentIndex] = {}
|
|
||||||
}
|
|
||||||
if (!expandedSubComponentPieceCustomFields[componentIndex]) {
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex] = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ensureComponentPieceState = (componentIndex, pieceIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
if (!expandedComponentPieceCustomFields[componentIndex][pieceIndex]) {
|
|
||||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex] = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ensureSubComponentState = (componentIndex, subIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
if (!expandedSubComponentCustomFields[componentIndex][subIndex]) {
|
|
||||||
expandedSubComponentCustomFields[componentIndex][subIndex] = []
|
|
||||||
}
|
|
||||||
if (!expandedSubComponentPieces[componentIndex][subIndex]) {
|
|
||||||
expandedSubComponentPieces[componentIndex][subIndex] = []
|
|
||||||
}
|
|
||||||
if (!expandedSubComponentPieceCustomFields[componentIndex][subIndex]) {
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex] = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ensureSubComponentPieceState = (componentIndex, subIndex, pieceIndex) => {
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
if (!expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex]) {
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex] = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isComponentExpanded = (index) => {
|
|
||||||
if (expandedComponents.value[index] === undefined) {
|
|
||||||
expandedComponents.value[index] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedComponents.value[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleComponentDetails = (index) => {
|
|
||||||
expandedComponents.value[index] = !isComponentExpanded(index)
|
|
||||||
if (!expandedComponents.value[index]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isComponentCustomFieldExpanded = (componentIndex, fieldIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
if (expandedComponentCustomFields[componentIndex][fieldIndex] === undefined) {
|
|
||||||
expandedComponentCustomFields[componentIndex][fieldIndex] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedComponentCustomFields[componentIndex][fieldIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleComponentCustomFieldDetails = (componentIndex, fieldIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedComponentCustomFields[componentIndex][fieldIndex] = !isComponentCustomFieldExpanded(componentIndex, fieldIndex)
|
|
||||||
if (!expandedComponentCustomFields[componentIndex][fieldIndex]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isComponentPieceExpanded = (componentIndex, pieceIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
if (expandedComponentPieces[componentIndex][pieceIndex] === undefined) {
|
|
||||||
expandedComponentPieces[componentIndex][pieceIndex] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedComponentPieces[componentIndex][pieceIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleComponentPieceDetails = (componentIndex, pieceIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedComponentPieces[componentIndex][pieceIndex] = !isComponentPieceExpanded(componentIndex, pieceIndex)
|
|
||||||
if (!expandedComponentPieces[componentIndex][pieceIndex]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isComponentPieceCustomFieldExpanded = (componentIndex, pieceIndex, fieldIndex) => {
|
|
||||||
ensureComponentPieceState(componentIndex, pieceIndex)
|
|
||||||
if (expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex] === undefined) {
|
|
||||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleComponentPieceCustomFieldDetails = (componentIndex, pieceIndex, fieldIndex) => {
|
|
||||||
ensureComponentPieceState(componentIndex, pieceIndex)
|
|
||||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex] = !isComponentPieceCustomFieldExpanded(componentIndex, pieceIndex, fieldIndex)
|
|
||||||
if (!expandedComponentPieceCustomFields[componentIndex][pieceIndex][fieldIndex]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSubComponentExpanded = (componentIndex, subIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
if (expandedSubComponents[componentIndex][subIndex] === undefined) {
|
|
||||||
expandedSubComponents[componentIndex][subIndex] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedSubComponents[componentIndex][subIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleSubComponentDetails = (componentIndex, subIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedSubComponents[componentIndex][subIndex] = !isSubComponentExpanded(componentIndex, subIndex)
|
|
||||||
if (!expandedSubComponents[componentIndex][subIndex]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSubComponentCustomFieldExpanded = (componentIndex, subIndex, fieldIndex) => {
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
if (expandedSubComponentCustomFields[componentIndex][subIndex][fieldIndex] === undefined) {
|
|
||||||
expandedSubComponentCustomFields[componentIndex][subIndex][fieldIndex] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedSubComponentCustomFields[componentIndex][subIndex][fieldIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleSubComponentCustomFieldDetails = (componentIndex, subIndex, fieldIndex) => {
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
expandedSubComponentCustomFields[componentIndex][subIndex][fieldIndex] = !isSubComponentCustomFieldExpanded(componentIndex, subIndex, fieldIndex)
|
|
||||||
if (!expandedSubComponentCustomFields[componentIndex][subIndex][fieldIndex]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSubComponentPieceExpanded = (componentIndex, subIndex, pieceIndex) => {
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
if (expandedSubComponentPieces[componentIndex][subIndex][pieceIndex] === undefined) {
|
|
||||||
expandedSubComponentPieces[componentIndex][subIndex][pieceIndex] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedSubComponentPieces[componentIndex][subIndex][pieceIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleSubComponentPieceDetails = (componentIndex, subIndex, pieceIndex) => {
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
expandedSubComponentPieces[componentIndex][subIndex][pieceIndex] = !isSubComponentPieceExpanded(componentIndex, subIndex, pieceIndex)
|
|
||||||
if (!expandedSubComponentPieces[componentIndex][subIndex][pieceIndex]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSubComponentPieceCustomFieldExpanded = (componentIndex, subIndex, pieceIndex, fieldIndex) => {
|
|
||||||
ensureSubComponentPieceState(componentIndex, subIndex, pieceIndex)
|
|
||||||
if (expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex][fieldIndex] === undefined) {
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex][fieldIndex] = allExpanded.value
|
|
||||||
}
|
|
||||||
return expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex][fieldIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleSubComponentPieceCustomFieldDetails = (componentIndex, subIndex, pieceIndex, fieldIndex) => {
|
|
||||||
ensureSubComponentPieceState(componentIndex, subIndex, pieceIndex)
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex][fieldIndex] = !isSubComponentPieceCustomFieldExpanded(componentIndex, subIndex, pieceIndex, fieldIndex)
|
|
||||||
if (!expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex][fieldIndex]) {
|
|
||||||
allExpanded.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const reorderStore = (store) => {
|
|
||||||
const reordered = {}
|
|
||||||
Object.keys(store)
|
|
||||||
.map(Number)
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
.forEach((key, position) => {
|
|
||||||
reordered[position] = store[key]
|
|
||||||
})
|
|
||||||
Object.keys(store).forEach(key => delete store[key])
|
|
||||||
Object.assign(store, reordered)
|
|
||||||
}
|
|
||||||
|
|
||||||
const reorderNestedStore = (store, componentIndex) => {
|
|
||||||
const entries = store[componentIndex] || {}
|
|
||||||
const reordered = {}
|
|
||||||
Object.keys(entries)
|
|
||||||
.map(Number)
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
.forEach((key, position) => {
|
|
||||||
reordered[position] = entries[key]
|
|
||||||
})
|
|
||||||
Object.keys(entries).forEach(key => delete entries[key])
|
|
||||||
Object.assign(entries, reordered)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearExpansionState = () => {
|
|
||||||
expandedComponents.value = []
|
|
||||||
Object.keys(expandedComponentCustomFields).forEach(key => delete expandedComponentCustomFields[key])
|
|
||||||
Object.keys(expandedComponentPieces).forEach(key => delete expandedComponentPieces[key])
|
|
||||||
Object.keys(expandedComponentPieceCustomFields).forEach(key => delete expandedComponentPieceCustomFields[key])
|
|
||||||
Object.keys(expandedSubComponents).forEach(key => delete expandedSubComponents[key])
|
|
||||||
Object.keys(expandedSubComponentCustomFields).forEach(key => delete expandedSubComponentCustomFields[key])
|
|
||||||
Object.keys(expandedSubComponentPieces).forEach(key => delete expandedSubComponentPieces[key])
|
|
||||||
Object.keys(expandedSubComponentPieceCustomFields).forEach(key => delete expandedSubComponentPieceCustomFields[key])
|
|
||||||
}
|
|
||||||
|
|
||||||
const setAllExpanded = (value) => {
|
|
||||||
clearExpansionState()
|
|
||||||
allExpanded.value = value
|
|
||||||
expandedComponents.value = components.value.map(() => value)
|
|
||||||
|
|
||||||
components.value.forEach((component, componentIndex) => {
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedComponentCustomFields[componentIndex] = (component.customFields || []).map(() => value)
|
|
||||||
expandedComponentPieces[componentIndex] = (component.pieces || []).map(() => value)
|
|
||||||
expandedComponentPieceCustomFields[componentIndex] = {}
|
|
||||||
;(component.pieces || []).forEach((piece, pieceIndex) => {
|
|
||||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex] = (piece.customFields || []).map(() => value)
|
|
||||||
})
|
|
||||||
|
|
||||||
expandedSubComponents[componentIndex] = (component.subComponents || []).map(() => value)
|
|
||||||
expandedSubComponentCustomFields[componentIndex] = {}
|
|
||||||
expandedSubComponentPieces[componentIndex] = {}
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex] = {}
|
|
||||||
|
|
||||||
;(component.subComponents || []).forEach((subComponent, subIndex) => {
|
|
||||||
expandedSubComponentCustomFields[componentIndex][subIndex] = (subComponent.customFields || []).map(() => value)
|
|
||||||
expandedSubComponentPieces[componentIndex][subIndex] = (subComponent.pieces || []).map(() => value)
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex] = {}
|
|
||||||
|
|
||||||
;(subComponent.pieces || []).forEach((piece, pieceIndex) => {
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex] = (piece.customFields || []).map(() => value)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleAllComponents = () => {
|
|
||||||
setAllExpanded(!allExpanded.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const initializeExpansionState = () => {
|
|
||||||
setAllExpanded(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
initializeExpansionState()
|
|
||||||
})
|
|
||||||
|
|
||||||
const addComponent = () => {
|
|
||||||
components.value.push({
|
|
||||||
name: '',
|
|
||||||
reference: '',
|
|
||||||
constructeur: '',
|
|
||||||
prix: null,
|
|
||||||
pieces: [],
|
|
||||||
customFields: [],
|
|
||||||
subComponents: []
|
|
||||||
})
|
|
||||||
emit('update:modelValue', components.value)
|
|
||||||
expandedComponents.value.push(allExpanded.value)
|
|
||||||
const newIndex = components.value.length - 1
|
|
||||||
ensureComponentState(newIndex)
|
|
||||||
expandedComponentCustomFields[newIndex] = []
|
|
||||||
expandedComponentPieces[newIndex] = []
|
|
||||||
expandedComponentPieceCustomFields[newIndex] = {}
|
|
||||||
expandedSubComponents[newIndex] = []
|
|
||||||
expandedSubComponentCustomFields[newIndex] = {}
|
|
||||||
expandedSubComponentPieces[newIndex] = {}
|
|
||||||
expandedSubComponentPieceCustomFields[newIndex] = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeComponent = (index) => {
|
|
||||||
components.value.splice(index, 1)
|
|
||||||
emit('update:modelValue', components.value)
|
|
||||||
expandedComponents.value.splice(index, 1)
|
|
||||||
delete expandedComponentCustomFields[index]
|
|
||||||
delete expandedComponentPieces[index]
|
|
||||||
delete expandedComponentPieceCustomFields[index]
|
|
||||||
delete expandedSubComponents[index]
|
|
||||||
delete expandedSubComponentCustomFields[index]
|
|
||||||
delete expandedSubComponentPieces[index]
|
|
||||||
delete expandedSubComponentPieceCustomFields[index]
|
|
||||||
reorderStore(expandedComponentCustomFields)
|
|
||||||
reorderStore(expandedComponentPieces)
|
|
||||||
reorderStore(expandedComponentPieceCustomFields)
|
|
||||||
reorderStore(expandedSubComponents)
|
|
||||||
reorderStore(expandedSubComponentCustomFields)
|
|
||||||
reorderStore(expandedSubComponentPieces)
|
|
||||||
reorderStore(expandedSubComponentPieceCustomFields)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addComponentCustomField = (componentIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (!component.customFields) {
|
|
||||||
component.customFields = []
|
|
||||||
}
|
|
||||||
component.customFields.push({
|
|
||||||
name: '',
|
|
||||||
type: '',
|
|
||||||
required: false,
|
|
||||||
optionsText: ''
|
|
||||||
})
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedComponentCustomFields[componentIndex].push(allExpanded.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeComponentCustomField = (componentIndex, fieldIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (component?.customFields) {
|
|
||||||
component.customFields.splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedComponentCustomFields[componentIndex].splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateComponentFieldOptions = (componentIndex, fieldIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (component?.customFields?.[fieldIndex]) {
|
|
||||||
component.customFields[fieldIndex].optionsText = component.customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addComponentPiece = (componentIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (!component.pieces) {
|
|
||||||
component.pieces = []
|
|
||||||
}
|
|
||||||
component.pieces.push({
|
|
||||||
name: '',
|
|
||||||
reference: '',
|
|
||||||
constructeur: '',
|
|
||||||
prix: null,
|
|
||||||
customFields: []
|
|
||||||
})
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedComponentPieces[componentIndex].push(allExpanded.value)
|
|
||||||
ensureComponentPieceState(componentIndex, component.pieces.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeComponentPiece = (componentIndex, pieceIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (component?.pieces) {
|
|
||||||
component.pieces.splice(pieceIndex, 1)
|
|
||||||
}
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedComponentPieces[componentIndex].splice(pieceIndex, 1)
|
|
||||||
delete expandedComponentPieceCustomFields[componentIndex][pieceIndex]
|
|
||||||
reorderNestedStore(expandedComponentPieceCustomFields, componentIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addComponentPieceCustomField = (componentIndex, pieceIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (!component?.pieces?.[pieceIndex].customFields) {
|
|
||||||
component.pieces[pieceIndex].customFields = []
|
|
||||||
}
|
|
||||||
component.pieces[pieceIndex].customFields.push({
|
|
||||||
name: '',
|
|
||||||
type: '',
|
|
||||||
required: false,
|
|
||||||
optionsText: ''
|
|
||||||
})
|
|
||||||
ensureComponentPieceState(componentIndex, pieceIndex)
|
|
||||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex].push(allExpanded.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeComponentPieceCustomField = (componentIndex, pieceIndex, fieldIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (component?.pieces?.[pieceIndex]?.customFields) {
|
|
||||||
component.pieces[pieceIndex].customFields.splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
ensureComponentPieceState(componentIndex, pieceIndex)
|
|
||||||
expandedComponentPieceCustomFields[componentIndex][pieceIndex].splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateComponentPieceFieldOptions = (componentIndex, pieceIndex, fieldIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (component?.pieces?.[pieceIndex]?.customFields?.[fieldIndex]) {
|
|
||||||
component.pieces[pieceIndex].customFields[fieldIndex].optionsText = component.pieces[pieceIndex].customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addSubComponent = (componentIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (!component.subComponents) {
|
|
||||||
component.subComponents = []
|
|
||||||
}
|
|
||||||
component.subComponents.push({
|
|
||||||
name: '',
|
|
||||||
reference: '',
|
|
||||||
constructeur: '',
|
|
||||||
prix: null,
|
|
||||||
customFields: [],
|
|
||||||
pieces: [],
|
|
||||||
subComponents: []
|
|
||||||
})
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedSubComponents[componentIndex].push(allExpanded.value)
|
|
||||||
const newIndex = component.subComponents.length - 1
|
|
||||||
ensureSubComponentState(componentIndex, newIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeSubComponent = (componentIndex, subIndex) => {
|
|
||||||
const component = components.value[componentIndex]
|
|
||||||
if (component?.subComponents) {
|
|
||||||
component.subComponents.splice(subIndex, 1)
|
|
||||||
}
|
|
||||||
ensureComponentState(componentIndex)
|
|
||||||
expandedSubComponents[componentIndex].splice(subIndex, 1)
|
|
||||||
delete expandedSubComponentCustomFields[componentIndex][subIndex]
|
|
||||||
delete expandedSubComponentPieces[componentIndex][subIndex]
|
|
||||||
delete expandedSubComponentPieceCustomFields[componentIndex][subIndex]
|
|
||||||
reorderNestedStore(expandedSubComponentCustomFields, componentIndex)
|
|
||||||
reorderNestedStore(expandedSubComponentPieces, componentIndex)
|
|
||||||
const pieceFieldEntries = expandedSubComponentPieceCustomFields[componentIndex] || {}
|
|
||||||
const reordered = {}
|
|
||||||
Object.keys(pieceFieldEntries)
|
|
||||||
.map(Number)
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
.forEach((key, position) => {
|
|
||||||
reordered[position] = pieceFieldEntries[key]
|
|
||||||
})
|
|
||||||
Object.keys(pieceFieldEntries).forEach(key => delete pieceFieldEntries[key])
|
|
||||||
Object.assign(pieceFieldEntries, reordered)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addSubComponentCustomField = (componentIndex, subIndex) => {
|
|
||||||
const subComponent = components.value[componentIndex]?.subComponents?.[subIndex]
|
|
||||||
if (!subComponent.customFields) {
|
|
||||||
subComponent.customFields = []
|
|
||||||
}
|
|
||||||
subComponent.customFields.push({
|
|
||||||
name: '',
|
|
||||||
type: '',
|
|
||||||
required: false,
|
|
||||||
optionsText: ''
|
|
||||||
})
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
expandedSubComponentCustomFields[componentIndex][subIndex].push(allExpanded.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeSubComponentCustomField = (componentIndex, subIndex, fieldIndex) => {
|
|
||||||
const subComponent = components.value[componentIndex]?.subComponents?.[subIndex]
|
|
||||||
if (subComponent?.customFields) {
|
|
||||||
subComponent.customFields.splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
expandedSubComponentCustomFields[componentIndex][subIndex].splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addSubComponentPiece = (componentIndex, subIndex) => {
|
|
||||||
const subComponent = components.value[componentIndex]?.subComponents?.[subIndex]
|
|
||||||
if (!subComponent.pieces) {
|
|
||||||
subComponent.pieces = []
|
|
||||||
}
|
|
||||||
subComponent.pieces.push({
|
|
||||||
name: '',
|
|
||||||
reference: '',
|
|
||||||
constructeur: '',
|
|
||||||
prix: null,
|
|
||||||
customFields: []
|
|
||||||
})
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
expandedSubComponentPieces[componentIndex][subIndex].push(allExpanded.value)
|
|
||||||
ensureSubComponentPieceState(componentIndex, subIndex, subComponent.pieces.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeSubComponentPiece = (componentIndex, subIndex, pieceIndex) => {
|
|
||||||
const subComponent = components.value[componentIndex]?.subComponents?.[subIndex]
|
|
||||||
if (subComponent?.pieces) {
|
|
||||||
subComponent.pieces.splice(pieceIndex, 1)
|
|
||||||
}
|
|
||||||
ensureSubComponentState(componentIndex, subIndex)
|
|
||||||
expandedSubComponentPieces[componentIndex][subIndex].splice(pieceIndex, 1)
|
|
||||||
if (expandedSubComponentPieceCustomFields[componentIndex]?.[subIndex]) {
|
|
||||||
delete expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex]
|
|
||||||
const store = expandedSubComponentPieceCustomFields[componentIndex][subIndex]
|
|
||||||
const reordered = {}
|
|
||||||
Object.keys(store || {})
|
|
||||||
.map(Number)
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
.forEach((key, position) => {
|
|
||||||
reordered[position] = store[key]
|
|
||||||
})
|
|
||||||
Object.keys(store || {}).forEach(key => delete store[key])
|
|
||||||
Object.assign(store || {}, reordered)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addSubComponentPieceCustomField = (componentIndex, subIndex, pieceIndex) => {
|
|
||||||
const piece = components.value[componentIndex]?.subComponents?.[subIndex]?.pieces?.[pieceIndex]
|
|
||||||
if (!piece.customFields) {
|
|
||||||
piece.customFields = []
|
|
||||||
}
|
|
||||||
piece.customFields.push({
|
|
||||||
name: '',
|
|
||||||
type: '',
|
|
||||||
required: false,
|
|
||||||
optionsText: ''
|
|
||||||
})
|
|
||||||
ensureSubComponentPieceState(componentIndex, subIndex, pieceIndex)
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex].push(allExpanded.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeSubComponentPieceCustomField = (componentIndex, subIndex, pieceIndex, fieldIndex) => {
|
|
||||||
const piece = components.value[componentIndex]?.subComponents?.[subIndex]?.pieces?.[pieceIndex]
|
|
||||||
if (piece?.customFields) {
|
|
||||||
piece.customFields.splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
ensureSubComponentPieceState(componentIndex, subIndex, pieceIndex)
|
|
||||||
expandedSubComponentPieceCustomFields[componentIndex][subIndex][pieceIndex].splice(fieldIndex, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateSubComponentPieceFieldOptions = (componentIndex, subIndex, pieceIndex, fieldIndex) => {
|
|
||||||
const piece = components.value[componentIndex]?.subComponents?.[subIndex]?.pieces?.[pieceIndex]
|
|
||||||
if (piece?.customFields?.[fieldIndex]) {
|
|
||||||
piece.customFields[fieldIndex].optionsText = piece.customFields[fieldIndex].optionsText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
import { useProfileSession } from '#imports'
|
import { useProfileSession } from "#imports";
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async (to) => {
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
const { ensureSession, fetchCurrentProfile, activeProfile } = useProfileSession()
|
const { ensureSession, fetchCurrentProfile, activeProfile } =
|
||||||
await ensureSession()
|
useProfileSession();
|
||||||
|
await ensureSession();
|
||||||
|
|
||||||
const rawPath = to?.path ?? ''
|
const rawPath = to?.path ?? "";
|
||||||
const normalizedPath = rawPath.startsWith('/') ? rawPath : `/${rawPath}`
|
const normalizedPath = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
||||||
const fullPath = to?.fullPath ?? normalizedPath
|
const fullPath = to?.fullPath ?? normalizedPath;
|
||||||
const routeName = typeof to?.name === 'string' ? to.name : ''
|
const routeName = typeof to?.name === "string" ? to.name : "";
|
||||||
const isProfilesRoute =
|
const isProfilesRoute =
|
||||||
normalizedPath.startsWith('/profiles') ||
|
normalizedPath.startsWith("/profiles") ||
|
||||||
fullPath.startsWith('/profiles') ||
|
fullPath.startsWith("/profiles") ||
|
||||||
routeName.startsWith('profiles')
|
routeName.startsWith("profiles");
|
||||||
|
|
||||||
if (process.client && !activeProfile.value) {
|
if (process.client && !activeProfile.value) {
|
||||||
await fetchCurrentProfile()
|
await fetchCurrentProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.client && !activeProfile.value && !isProfilesRoute) {
|
if (process.client && !activeProfile.value && !isProfilesRoute) {
|
||||||
return navigateTo('/profiles')
|
return navigateTo("/profiles");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -203,7 +203,12 @@ import { useComponentModels } from '~/composables/useComponentModels'
|
|||||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
import ComponentModelStructureEditor from '~/components/ComponentModelStructureEditor.vue'
|
import ComponentModelStructureEditor from '~/components/ComponentModelStructureEditor.vue'
|
||||||
import { formatStructurePreview } from '~/shared/modelUtils'
|
import {
|
||||||
|
formatStructurePreview,
|
||||||
|
defaultStructure,
|
||||||
|
cloneStructure,
|
||||||
|
normalizeStructureForSave,
|
||||||
|
} from '~/shared/modelUtils'
|
||||||
import { formatFrenchDate } from '~/utils/date'
|
import { formatFrenchDate } from '~/utils/date'
|
||||||
import IconLucidePlus from '~icons/lucide/plus'
|
import IconLucidePlus from '~icons/lucide/plus'
|
||||||
import IconLucideLayers from '~icons/lucide/layers'
|
import IconLucideLayers from '~icons/lucide/layers'
|
||||||
@@ -231,7 +236,7 @@ const form = reactive({
|
|||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
typeComposantId: '',
|
typeComposantId: '',
|
||||||
structure: {}
|
structure: defaultStructure(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -253,7 +258,7 @@ const startCreate = () => {
|
|||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
typeComposantId: selectedType.value !== 'all' ? selectedType.value : '',
|
typeComposantId: selectedType.value !== 'all' ? selectedType.value : '',
|
||||||
structure: {}
|
structure: defaultStructure(),
|
||||||
}
|
}
|
||||||
ensureTypeSelected()
|
ensureTypeSelected()
|
||||||
}
|
}
|
||||||
@@ -265,7 +270,7 @@ const startEdit = (model) => {
|
|||||||
name: model.name,
|
name: model.name,
|
||||||
description: model.description || '',
|
description: model.description || '',
|
||||||
typeComposantId: model.typeComposantId || model.typeComposant?.id || '',
|
typeComposantId: model.typeComposantId || model.typeComposant?.id || '',
|
||||||
structure: model.structure || {}
|
structure: cloneStructure(model.structure || defaultStructure()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +313,7 @@ const handleSubmit = async () => {
|
|||||||
name: form.data.name.trim(),
|
name: form.data.name.trim(),
|
||||||
description: form.data.description.trim() || undefined,
|
description: form.data.description.trim() || undefined,
|
||||||
typeComposantId: form.data.typeComposantId,
|
typeComposantId: form.data.typeComposantId,
|
||||||
structure: form.data.structure || {}
|
structure: normalizeStructureForSave(form.data.structure),
|
||||||
})
|
})
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
showError(result.error || 'Impossible de créer le modèle')
|
showError(result.error || 'Impossible de créer le modèle')
|
||||||
@@ -320,7 +325,7 @@ const handleSubmit = async () => {
|
|||||||
name: form.data.name.trim(),
|
name: form.data.name.trim(),
|
||||||
description: form.data.description.trim() || undefined,
|
description: form.data.description.trim() || undefined,
|
||||||
typeComposantId: form.data.typeComposantId,
|
typeComposantId: form.data.typeComposantId,
|
||||||
structure: form.data.structure || {}
|
structure: normalizeStructureForSave(form.data.structure),
|
||||||
})
|
})
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
showError(result.error || 'Impossible de mettre à jour le modèle')
|
showError(result.error || 'Impossible de mettre à jour le modèle')
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<h2 class="text-2xl font-bold text-gray-800">
|
<h2 class="text-2xl font-bold text-gray-800">
|
||||||
Squelettes de machine
|
Squelettes de machine
|
||||||
</h2>
|
</h2>
|
||||||
<NuxtLink to="/generator" class="btn btn-primary">
|
<NuxtLink to="/machine-skeleton/new" class="btn btn-primary">
|
||||||
<IconLucidePlus
|
<IconLucidePlus
|
||||||
class="w-5 h-5 mr-2"
|
class="w-5 h-5 mr-2"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
Reference in New Issue
Block a user