fix: stabilize constructeur selector ui

This commit is contained in:
Matthieu
2025-09-17 16:21:42 +02:00
parent 0a95b90553
commit 8a32ef4bbc
4 changed files with 92 additions and 55 deletions

View File

@@ -81,7 +81,9 @@
<label class="label"><span class="label-text font-medium">Constructeur</span></label> <label class="label"><span class="label-text font-medium">Constructeur</span></label>
<ConstructeurSelect <ConstructeurSelect
v-if="isEditMode" v-if="isEditMode"
v-model="component.constructeurId" class="w-full"
:model-value="component.constructeurId || component.constructeur?.id || null"
@update:modelValue="handleConstructeurChange"
/> />
<div v-else class="input input-bordered input-sm bg-base-200"> <div v-else class="input input-bordered input-sm bg-base-200">
<div class="flex flex-col"> <div class="flex flex-col">
@@ -288,6 +290,11 @@ const documentsLoaded = ref(!!(props.component.documents && props.component.docu
const componentDocuments = computed(() => props.component.documents || []) const componentDocuments = computed(() => props.component.documents || [])
const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType }) const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType })
const handleConstructeurChange = async (value) => {
props.component.constructeurId = value
await updateComponent()
}
const { uploadDocuments, deleteDocument, loadDocumentsByComponent } = useDocuments() const { uploadDocuments, deleteDocument, loadDocumentsByComponent } = useDocuments()
watch( watch(
@@ -301,16 +308,6 @@ watch(
{ immediate: true } { immediate: true }
) )
watch(
() => props.component.constructeurId,
(newVal, oldVal) => {
if (!props.isEditMode) return
if (oldVal === undefined) return
if (newVal !== oldVal) {
updateComponent()
}
}
)
watch( watch(
() => props.component.documents, () => props.component.documents,

View File

@@ -20,17 +20,21 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16l4-4 4 4m0-8l-4 4-4-4" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16l4-4 4 4m0-8l-4 4-4-4" />
</svg> </svg>
</button> </button>
<ul <div
v-if="openDropdown" v-if="openDropdown"
class="menu bg-base-100 border border-base-200 rounded-box shadow-lg mt-1 w-full max-h-48 overflow-y-auto absolute z-20" class="absolute z-20 mt-1 w-full max-h-48 overflow-y-auto bg-base-100 border border-base-200 rounded-box shadow-lg flex flex-col"
> >
<li v-if="options.length === 0" class="px-3 py-2 text-xs text-gray-500"> <div
v-if="options.length === 0"
class="px-3 py-2 text-xs text-gray-500"
>
Aucun constructeur trouvé Aucun constructeur trouvé
</li> </div>
<li <button
v-for="option in options" v-for="option in options"
:key="option.id" :key="option.id"
class="px-3 py-2" type="button"
class="w-full text-left px-3 py-2 hover:bg-base-200 focus:bg-base-200 focus:outline-none"
@click="selectOption(option)" @click="selectOption(option)"
> >
<div class="flex flex-col"> <div class="flex flex-col">
@@ -39,8 +43,8 @@
{{ [option.email, option.phone].filter(Boolean).join(' • ') || '—' }} {{ [option.email, option.phone].filter(Boolean).join(' • ') || '—' }}
</span> </span>
</div> </div>
</li> </button>
</ul> </div>
</div> </div>
<button type="button" class="btn btn-outline btn-sm" @click="openCreateModal = true"> <button type="button" class="btn btn-outline btn-sm" @click="openCreateModal = true">
Nouveau Nouveau
@@ -111,6 +115,23 @@ const openCreateModal = ref(false)
const creating = ref(false) const creating = ref(false)
const options = ref([]) const options = ref([])
let searchTimeout = null let searchTimeout = null
let lastSearchTerm = ''
const applyOptions = (items = []) => {
const selectedId = props.modelValue
const cloned = [...items]
const limited = cloned.slice(0, 10)
if (selectedId && !limited.some(item => item.id === selectedId)) {
const selected = cloned.find(item => item.id === selectedId)
if (selected) {
if (limited.length >= 10) limited.pop()
limited.unshift(selected)
}
}
options.value = limited
}
const createForm = ref({ const createForm = ref({
name: '', name: '',
@@ -140,10 +161,16 @@ watch(
) )
const ensureOptionsLoaded = async (force = false) => { const ensureOptionsLoaded = async (force = false) => {
if (!force && !searchTerm.value && constructeurs.value.length) {
applyOptions(constructeurs.value)
return
}
if (!force && searchTerm.value === lastSearchTerm && options.value.length) return
if (options.value.length && !force) return if (options.value.length && !force) return
const result = await searchConstructeurs(searchTerm.value) const result = await searchConstructeurs(searchTerm.value)
if (result.success) { if (result.success) {
options.value = result.data applyOptions(result.data || [])
lastSearchTerm = searchTerm.value
} }
} }
@@ -151,9 +178,16 @@ const onSearch = () => {
openDropdown.value = true openDropdown.value = true
clearTimeout(searchTimeout) clearTimeout(searchTimeout)
searchTimeout = setTimeout(async () => { searchTimeout = setTimeout(async () => {
if (!searchTerm.value && constructeurs.value.length) {
applyOptions(constructeurs.value)
lastSearchTerm = ''
return
}
if (searchTerm.value === lastSearchTerm) return
const result = await searchConstructeurs(searchTerm.value) const result = await searchConstructeurs(searchTerm.value)
if (result.success) { if (result.success) {
options.value = result.data applyOptions(result.data || [])
lastSearchTerm = searchTerm.value
} }
}, 250) }, 250)
} }
@@ -184,6 +218,17 @@ const handleCreate = async () => {
} }
} }
watch(
constructeurs,
(list) => {
applyOptions(list || [])
if (!searchTerm.value) {
lastSearchTerm = ''
}
},
{ immediate: true }
)
const clickHandler = (event) => { const clickHandler = (event) => {
const element = event.target const element = event.target
if (element && element.closest) { if (element && element.closest) {
@@ -209,10 +254,3 @@ onBeforeUnmount(() => {
clearTimeout(searchTimeout) clearTimeout(searchTimeout)
}) })
</script> </script>
watch(
constructeurs,
(list) => {
options.value = [...list]
},
{ immediate: true }
)

View File

@@ -42,7 +42,9 @@
</span> </span>
<ConstructeurSelect <ConstructeurSelect
v-else v-else
v-model="piece.constructeurId" class="w-full"
:model-value="piece.constructeurId || piece.constructeur?.id || null"
@update:modelValue="handleConstructeurChange"
/> />
</div> </div>
<div> <div>
@@ -260,6 +262,11 @@ const documentsLoaded = ref(!!(props.piece.documents && props.piece.documents.le
const pieceDocuments = computed(() => props.piece.documents || []) const pieceDocuments = computed(() => props.piece.documents || [])
const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType }) const documentIcon = (doc) => getFileIcon({ name: doc.filename || doc.name, mime: doc.mimeType })
const handleConstructeurChange = (value) => {
props.piece.constructeurId = value
updatePiece()
}
const { uploadDocuments, deleteDocument, loadDocumentsByPiece } = useDocuments() const { uploadDocuments, deleteDocument, loadDocumentsByPiece } = useDocuments()
const refreshDocuments = async () => { const refreshDocuments = async () => {
@@ -337,16 +344,6 @@ watch(
} }
) )
watch(
() => props.piece.constructeurId,
(newVal, oldVal) => {
if (!props.isEditMode) return
if (oldVal === undefined) return
if (newVal !== oldVal) {
updatePiece()
}
}
)
// Méthodes pour gérer les champs personnalisés // Méthodes pour gérer les champs personnalisés
const setCustomFieldValue = (fieldValueId, value) => { const setCustomFieldValue = (fieldValueId, value) => {

View File

@@ -107,15 +107,17 @@
</label> </label>
<ConstructeurSelect <ConstructeurSelect
v-if="isEditMode" v-if="isEditMode"
class="w-full"
:key="machine.value?.id" :key="machine.value?.id"
v-model="machineConstructeurId" :model-value="machineConstructeurId"
placeholder="Rechercher un constructeur..." placeholder="Rechercher un constructeur..."
@update:modelValue="handleMachineConstructeurChange"
/> />
<div v-else class="input input-bordered bg-base-200"> <div v-else class="input input-bordered bg-base-200">
<div class="flex flex-col"> <div class="flex flex-col">
<span class="font-medium">{{ machine.value?.constructeur?.name || 'Non défini' }}</span> <span class="font-medium">{{ machineConstructeurDisplay?.name || 'Non défini' }}</span>
<span class="text-xs text-gray-500"> <span class="text-xs text-gray-500">
{{ [machine.value?.constructeur?.email, machine.value?.constructeur?.phone].filter(Boolean).join(' • ') }} {{ [machineConstructeurDisplay?.email, machineConstructeurDisplay?.phone].filter(Boolean).join(' • ') || '' }}
</span> </span>
</div> </div>
</div> </div>
@@ -366,6 +368,7 @@ import { getFileIcon } from '~/utils/fileIcons'
import ComponentHierarchy from '~/components/ComponentHierarchy.vue' import ComponentHierarchy from '~/components/ComponentHierarchy.vue'
import DocumentUpload from '~/components/DocumentUpload.vue' import DocumentUpload from '~/components/DocumentUpload.vue'
import ConstructeurSelect from '~/components/ConstructeurSelect.vue' import ConstructeurSelect from '~/components/ConstructeurSelect.vue'
import { useConstructeurs } from '~/composables/useConstructeurs'
const route = useRoute() const route = useRoute()
const machineId = route.params.id const machineId = route.params.id
@@ -401,11 +404,18 @@ const machine = ref(null)
const components = ref([]) const components = ref([])
const pieces = ref([]) const pieces = ref([])
const { constructeurs, loadConstructeurs } = useConstructeurs()
// Champs de la machine // Champs de la machine
const machineName = ref('') const machineName = ref('')
const machineReference = ref('') const machineReference = ref('')
const machineEmplacement = ref('') const machineEmplacement = ref('')
const machineConstructeurId = ref(null) const machineConstructeurId = ref(null)
const machineConstructeurDisplay = computed(() => {
const id = machineConstructeurId.value || machine.value?.constructeur?.id || machine.value?.constructeurId
if (!id) return machine.value?.constructeur || null
return constructeurs.value.find(item => item.id === id) || machine.value?.constructeur || null
})
// Valeurs des champs personnalisés de la machine // Valeurs des champs personnalisés de la machine
const machineCustomFieldValues = reactive({}) const machineCustomFieldValues = reactive({})
@@ -413,18 +423,11 @@ const machineCustomFieldValues = reactive({})
const machineDocumentFiles = ref([]) const machineDocumentFiles = ref([])
const machineDocumentsUploading = ref(false) const machineDocumentsUploading = ref(false)
const machineDocumentsLoaded = ref(false) const machineDocumentsLoaded = ref(false)
const machineConstructeurInitialized = ref(false)
watch(machineConstructeurId, (newValue, oldValue) => { const handleMachineConstructeurChange = async (value) => {
if (!machine.value) return machineConstructeurId.value = value
if (!machineConstructeurInitialized.value) { await updateMachineInfo()
machineConstructeurInitialized.value = true }
return
}
if (newValue !== oldValue) {
updateMachineInfo()
}
})
// Mode d'édition // Mode d'édition
const isEditMode = ref(false) const isEditMode = ref(false)
@@ -451,7 +454,6 @@ const initMachineFields = () => {
machineReference.value = machine.value.reference || '' machineReference.value = machine.value.reference || ''
machineEmplacement.value = machine.value.emplacement || '' machineEmplacement.value = machine.value.emplacement || ''
machineConstructeurId.value = machine.value.constructeurId || machine.value.constructeur?.id || null machineConstructeurId.value = machine.value.constructeurId || machine.value.constructeur?.id || null
machineConstructeurInitialized.value = false
} }
} }
@@ -839,6 +841,9 @@ const toggleEditMode = () => {
// Lifecycle // Lifecycle
onMounted(() => { onMounted(() => {
loadMachineData() loadMachineData()
if (!constructeurs.value.length) {
loadConstructeurs()
}
// Vérifier si on doit activer le mode édition depuis l'URL // Vérifier si on doit activer le mode édition depuis l'URL
const route = useRoute() const route = useRoute()