Allow component catalog to instantiate components without machine
This commit is contained in:
@@ -153,7 +153,7 @@
|
|||||||
Instancier un composant
|
Instancier un composant
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
Sélectionnez la machine et le requirement cible puis ajustez les informations d'override avant la création.
|
Renseignez les informations du composant instancié à partir du squelette de la catégorie sélectionnée.
|
||||||
</p>
|
</p>
|
||||||
<p v-if="selectedType" class="badge badge-outline badge-sm">
|
<p v-if="selectedType" class="badge badge-outline badge-sm">
|
||||||
Catégorie : {{ selectedType.name }}
|
Catégorie : {{ selectedType.name }}
|
||||||
@@ -161,79 +161,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="space-y-4" @submit.prevent="submitCreation">
|
<form class="space-y-4" @submit.prevent="submitCreation">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Machine cible</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
v-model="creationForm.machineId"
|
|
||||||
class="select select-bordered select-sm md:select-md"
|
|
||||||
:disabled="machinesLoading || submitting"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="">Sélectionner une machine</option>
|
|
||||||
<option
|
|
||||||
v-for="machine in machines"
|
|
||||||
:key="machine.id"
|
|
||||||
:value="machine.id"
|
|
||||||
>
|
|
||||||
{{ machine.name }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<p v-if="machinesLoading" class="text-xs text-gray-500 mt-1">
|
|
||||||
Chargement des machines...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-control">
|
|
||||||
<label class="label">
|
|
||||||
<span class="label-text">Requirement</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
v-model="creationForm.requirementId"
|
|
||||||
class="select select-bordered select-sm md:select-md"
|
|
||||||
:disabled="requirementLoading || !requirementOptions.length || submitting"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="">Sélectionner un requirement</option>
|
|
||||||
<option
|
|
||||||
v-for="requirement in requirementOptions"
|
|
||||||
:key="requirement.id"
|
|
||||||
:value="requirement.id"
|
|
||||||
>
|
|
||||||
{{ resolveRequirementLabel(requirement) }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<p v-if="requirementLoading" class="text-xs text-gray-500 mt-1">
|
|
||||||
Chargement des requirements de la machine...
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
v-else-if="creationForm.machineId && !requirementOptions.length"
|
|
||||||
class="text-xs text-error mt-1"
|
|
||||||
>
|
|
||||||
Cette machine n'a pas de requirement de composant configuré.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="selectedRequirement"
|
|
||||||
class="rounded-lg border border-base-200 bg-base-200/60 p-4 text-xs text-base-content/80 space-y-1"
|
|
||||||
>
|
|
||||||
<div class="flex flex-wrap gap-2 items-center">
|
|
||||||
<span class="font-medium text-sm">Requirement sélectionné :</span>
|
|
||||||
<span class="badge badge-outline badge-sm">{{ resolveRequirementLabel(selectedRequirement) }}</span>
|
|
||||||
</div>
|
|
||||||
<p v-if="selectedRequirement.typeComposant?.name" class="text-xs">
|
|
||||||
Type attendu : {{ selectedRequirement.typeComposant.name }}
|
|
||||||
</p>
|
|
||||||
<p v-if="selectedRequirement.maxCount !== null && selectedRequirement.maxCount !== undefined" class="text-xs">
|
|
||||||
Capacité : {{ selectedRequirement.minCount ?? (selectedRequirement.required ? 1 : 0) }} -
|
|
||||||
{{ selectedRequirement.maxCount ?? '∞' }} élément(s)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
@@ -311,13 +238,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import ConstructeurSelect from '~/components/ConstructeurSelect.vue'
|
import ConstructeurSelect from '~/components/ConstructeurSelect.vue'
|
||||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||||
import { useMachines } from '~/composables/useMachines'
|
|
||||||
import { useComposants } from '~/composables/useComposants'
|
import { useComposants } from '~/composables/useComposants'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
import { useApi } from '~/composables/useApi'
|
|
||||||
import { formatStructurePreview, sanitizeDefinitionOverrides } from '~/shared/modelUtils'
|
import { formatStructurePreview, sanitizeDefinitionOverrides } from '~/shared/modelUtils'
|
||||||
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
||||||
import type { ModelType } from '~/services/modelTypes'
|
import type { ModelType } from '~/services/modelTypes'
|
||||||
@@ -329,54 +254,26 @@ interface ComponentCatalogType extends ModelType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { componentTypes, loadComponentTypes, loadingComponentTypes } = useComponentTypes()
|
const { componentTypes, loadComponentTypes, loadingComponentTypes } = useComponentTypes()
|
||||||
const { machines, loadMachines, loading: machinesLoadingRef } = useMachines()
|
|
||||||
const { createComposant } = useComposants()
|
const { createComposant } = useComposants()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { apiCall } = useApi()
|
|
||||||
|
|
||||||
const creationModalOpen = ref(false)
|
const creationModalOpen = ref(false)
|
||||||
const selectedType = ref<ComponentCatalogType | null>(null)
|
const selectedType = ref<ComponentCatalogType | null>(null)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const requirementLoading = ref(false)
|
|
||||||
const creationForm = reactive({
|
const creationForm = reactive({
|
||||||
machineId: '' as string,
|
|
||||||
requirementId: '' as string,
|
|
||||||
name: '' as string,
|
name: '' as string,
|
||||||
reference: '' as string,
|
reference: '' as string,
|
||||||
constructeurId: null as string | null,
|
constructeurId: null as string | null,
|
||||||
prix: '' as string,
|
prix: '' as string,
|
||||||
})
|
})
|
||||||
|
|
||||||
const machineRequirementCache = reactive<Record<string, { requirements: any[] }>>({})
|
|
||||||
const lastSuggestedName = ref('')
|
|
||||||
let requirementRequestToken = 0
|
|
||||||
|
|
||||||
const loadingTypes = computed(() => loadingComponentTypes.value)
|
const loadingTypes = computed(() => loadingComponentTypes.value)
|
||||||
const componentTypeList = computed<ComponentCatalogType[]>(() =>
|
const componentTypeList = computed<ComponentCatalogType[]>(() =>
|
||||||
(componentTypes.value || [])
|
(componentTypes.value || [])
|
||||||
.filter((item: any) => item?.category === 'COMPONENT') as ComponentCatalogType[],
|
.filter((item: any) => item?.category === 'COMPONENT') as ComponentCatalogType[],
|
||||||
)
|
)
|
||||||
const machinesLoading = computed(() => machinesLoadingRef.value)
|
|
||||||
|
|
||||||
const requirementOptions = computed(() => {
|
const canSubmit = computed(() => Boolean(selectedType.value && !submitting.value))
|
||||||
const machineId = creationForm.machineId
|
|
||||||
if (!machineId) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const entry = machineRequirementCache[machineId]
|
|
||||||
if (!entry) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return Array.isArray(entry.requirements) ? entry.requirements : []
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedRequirement = computed(() => {
|
|
||||||
return requirementOptions.value.find((requirement: any) => requirement.id === creationForm.requirementId) || null
|
|
||||||
})
|
|
||||||
|
|
||||||
const canSubmit = computed(() => {
|
|
||||||
return Boolean(creationForm.machineId && creationForm.requirementId && !submitting.value && !requirementLoading.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const getCategoryCustomFields = (type: ComponentCatalogType) => {
|
const getCategoryCustomFields = (type: ComponentCatalogType) => {
|
||||||
return Array.isArray(type?.customFields) ? type.customFields : []
|
return Array.isArray(type?.customFields) ? type.customFields : []
|
||||||
@@ -445,33 +342,22 @@ const resolveSubcomponentLabel = (node: Record<string, any>) => {
|
|||||||
return parts.length ? parts.join(' • ') : 'Sous-composant'
|
return parts.length ? parts.join(' • ') : 'Sous-composant'
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveRequirementLabel = (requirement: any) => {
|
|
||||||
return requirement?.label || requirement?.typeComposant?.name || 'Requirement'
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearCreationForm = () => {
|
const clearCreationForm = () => {
|
||||||
creationForm.machineId = ''
|
|
||||||
creationForm.requirementId = ''
|
|
||||||
creationForm.name = ''
|
creationForm.name = ''
|
||||||
creationForm.reference = ''
|
creationForm.reference = ''
|
||||||
creationForm.constructeurId = null
|
creationForm.constructeurId = null
|
||||||
creationForm.prix = ''
|
creationForm.prix = ''
|
||||||
lastSuggestedName.value = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetCreationFormForType = () => {
|
const resetCreationFormForType = () => {
|
||||||
clearCreationForm()
|
clearCreationForm()
|
||||||
creationForm.name = selectedType.value?.name || ''
|
creationForm.name = selectedType.value?.name || ''
|
||||||
lastSuggestedName.value = creationForm.name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const openCreationModal = async (type: ComponentCatalogType) => {
|
const openCreationModal = async (type: ComponentCatalogType) => {
|
||||||
selectedType.value = type
|
selectedType.value = type
|
||||||
resetCreationFormForType()
|
resetCreationFormForType()
|
||||||
creationModalOpen.value = true
|
creationModalOpen.value = true
|
||||||
if (!machines.value?.length) {
|
|
||||||
await loadMachines()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeCreationModal = () => {
|
const closeCreationModal = () => {
|
||||||
@@ -480,75 +366,14 @@ const closeCreationModal = () => {
|
|||||||
clearCreationForm()
|
clearCreationForm()
|
||||||
}
|
}
|
||||||
|
|
||||||
const ensureMachineRequirements = async (machineId: string) => {
|
|
||||||
if (!machineId || machineRequirementCache[machineId]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestId = ++requirementRequestToken
|
|
||||||
requirementLoading.value = true
|
|
||||||
try {
|
|
||||||
const result = await apiCall(`/machines/${machineId}`, { method: 'GET' })
|
|
||||||
if (result.success) {
|
|
||||||
const requirements = result.data?.typeMachine?.componentRequirements || []
|
|
||||||
machineRequirementCache[machineId] = { requirements }
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (requestId === requirementRequestToken) {
|
|
||||||
requirementLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => creationForm.machineId,
|
|
||||||
async (machineId) => {
|
|
||||||
creationForm.requirementId = ''
|
|
||||||
if (!machineId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await ensureMachineRequirements(machineId)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => creationForm.requirementId,
|
|
||||||
(requirementId) => {
|
|
||||||
if (!selectedType.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const requirement = requirementId ? selectedRequirement.value : null
|
|
||||||
const suggestion =
|
|
||||||
requirement?.label || requirement?.typeComposant?.name || selectedType.value?.name || ''
|
|
||||||
|
|
||||||
if (!creationForm.name || creationForm.name === lastSuggestedName.value) {
|
|
||||||
creationForm.name = suggestion
|
|
||||||
}
|
|
||||||
lastSuggestedName.value = suggestion
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const submitCreation = async () => {
|
const submitCreation = async () => {
|
||||||
if (!creationForm.machineId || !creationForm.requirementId) {
|
|
||||||
toast.showError('Sélectionnez une machine et un requirement avant de continuer.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!selectedType.value) {
|
if (!selectedType.value) {
|
||||||
toast.showError('Aucune catégorie sélectionnée.')
|
toast.showError('Aucune catégorie sélectionnée.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: Record<string, any> = {
|
const payload: Record<string, any> = {
|
||||||
machineId: creationForm.machineId,
|
typeComposantId: selectedType.value.id,
|
||||||
typeMachineComponentRequirementId: creationForm.requirementId,
|
|
||||||
}
|
|
||||||
|
|
||||||
const requirement = selectedRequirement.value
|
|
||||||
if (selectedType.value.id) {
|
|
||||||
const requirementTypeId = requirement?.typeComposantId || null
|
|
||||||
if (!requirementTypeId || requirementTypeId !== selectedType.value.id) {
|
|
||||||
payload.typeComposantId = selectedType.value.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const overrides = sanitizeDefinitionOverrides({
|
const overrides = sanitizeDefinitionOverrides({
|
||||||
@@ -579,9 +404,6 @@ const submitCreation = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.allSettled([
|
await loadComponentTypes()
|
||||||
loadComponentTypes(),
|
|
||||||
loadMachines(),
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user