Intégration fonctionnalites V1

This commit is contained in:
bastien
2026-07-04 22:30:18 +02:00
parent 55a558b536
commit 10ee859602
54 changed files with 4802 additions and 1018 deletions
@@ -0,0 +1,162 @@
<script setup>
import { filterInputClass } from '@/Components/Supervisor/ui.js';
import { onMounted, onUnmounted, ref, watch } from 'vue';
const props = defineProps({
address: {
type: String,
default: '',
},
city: {
type: String,
default: '',
},
zipCode: {
type: String,
default: '',
},
required: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['update:address', 'update:city', 'update:zipCode']);
const root = ref(null);
const query = ref(props.address ?? '');
const suggestions = ref([]);
const isOpen = ref(false);
const isLoading = ref(false);
const activeIndex = ref(-1);
let debounceTimer = null;
watch(
() => props.address,
(value) => {
if (value !== query.value) {
query.value = value ?? '';
}
},
);
const closeSuggestions = () => {
isOpen.value = false;
activeIndex.value = -1;
};
const searchAddresses = async () => {
if (query.value.trim().length < 3) {
suggestions.value = [];
closeSuggestions();
return;
}
isLoading.value = true;
try {
const response = await window.axios.get(route('supervisor.organizations.addresses.search'), {
params: { q: query.value.trim() },
});
suggestions.value = response.data ?? [];
isOpen.value = suggestions.value.length > 0;
activeIndex.value = -1;
} catch {
suggestions.value = [];
closeSuggestions();
} finally {
isLoading.value = false;
}
};
const onInput = () => {
emit('update:address', query.value);
clearTimeout(debounceTimer);
debounceTimer = setTimeout(searchAddresses, 300);
};
const selectSuggestion = (suggestion) => {
query.value = suggestion.address;
emit('update:address', suggestion.address);
emit('update:city', suggestion.city ?? '');
emit('update:zipCode', suggestion.zip_code ?? '');
closeSuggestions();
};
const onKeydown = (event) => {
if (!isOpen.value || suggestions.value.length === 0) {
return;
}
if (event.key === 'ArrowDown') {
event.preventDefault();
activeIndex.value = (activeIndex.value + 1) % suggestions.value.length;
} else if (event.key === 'ArrowUp') {
event.preventDefault();
activeIndex.value = activeIndex.value <= 0
? suggestions.value.length - 1
: activeIndex.value - 1;
} else if (event.key === 'Enter' && activeIndex.value >= 0) {
event.preventDefault();
selectSuggestion(suggestions.value[activeIndex.value]);
} else if (event.key === 'Escape') {
closeSuggestions();
}
};
const onClickOutside = (event) => {
if (root.value && !root.value.contains(event.target)) {
closeSuggestions();
}
};
onMounted(() => document.addEventListener('click', onClickOutside));
onUnmounted(() => {
document.removeEventListener('click', onClickOutside);
clearTimeout(debounceTimer);
});
</script>
<template>
<div ref="root" class="relative">
<input
v-model="query"
type="text"
autocomplete="off"
placeholder="Rechercher une adresse…"
:required="required"
:class="filterInputClass"
@input="onInput"
@focus="searchAddresses"
@keydown="onKeydown"
/>
<p v-if="isLoading" class="mt-1 text-xs text-slate-400">
Recherche en cours
</p>
<ul
v-if="isOpen && suggestions.length > 0"
class="absolute z-20 mt-1 max-h-56 w-full overflow-y-auto rounded-xl border border-slate-200 bg-white py-1 shadow-lg"
>
<li
v-for="(suggestion, index) in suggestions"
:key="`${suggestion.label}-${index}`"
>
<button
type="button"
class="block w-full px-3 py-2 text-left text-sm transition hover:bg-indigo-50"
:class="index === activeIndex ? 'bg-indigo-50 text-indigo-700' : 'text-slate-700'"
@mousedown.prevent="selectSuggestion(suggestion)"
>
<span class="block font-medium">{{ suggestion.address }}</span>
<span class="block text-xs text-slate-400">
{{ [suggestion.zip_code, suggestion.city].filter(Boolean).join(' ') }}
</span>
</button>
</li>
</ul>
</div>
</template>