feat: barre de recherche globale dans la navbar admin
Navbar admin :
- Barre de recherche persistante en haut de toutes les pages admin
- Recherche dans tous les index Meilisearch simultanément :
clients (5), NDD (5), sites (5), contacts (5), revendeurs (3)
- Résultats en dropdown glassmorphism avec icône par type
- Clic sur un résultat → page + tab correspondant :
Client → /admin/clients/{id}
NDD → /admin/clients/{id}?tab=ndd
Site → /admin/clients/{id}?tab=sites
Contact → /admin/clients/{id}?tab=contacts
Revendeur → /admin/revendeurs/{id}/edit
DashboardController::globalSearch :
- Route GET /admin/global-search?q=...
- Agrège les résultats de 5 index Meilisearch
- Retourne [{type, label, sub, url}]
app.js :
- Debounce 250ms, min 2 chars
- Badges type (Client, NDD, Site, Contact, Revendeur)
- Fermeture Escape / clic extérieur
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -213,4 +213,64 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Recherche entreprise (page creation client)
|
||||
initEntrepriseSearch();
|
||||
|
||||
// Recherche globale (navbar admin)
|
||||
const globalInput = document.getElementById('global-search');
|
||||
const globalResults = document.getElementById('global-search-results');
|
||||
if (globalInput && globalResults) {
|
||||
const typeIcons = {
|
||||
client: '👤',
|
||||
ndd: '🌐',
|
||||
site: '💻',
|
||||
contact: '👥',
|
||||
revendeur: '🏢',
|
||||
};
|
||||
const typeLabels = {
|
||||
client: 'Client',
|
||||
ndd: 'NDD',
|
||||
site: 'Site',
|
||||
contact: 'Contact',
|
||||
revendeur: 'Revendeur',
|
||||
};
|
||||
|
||||
let globalDebounce;
|
||||
globalInput.addEventListener('input', () => {
|
||||
clearTimeout(globalDebounce);
|
||||
const q = globalInput.value.trim();
|
||||
if (q.length < 2) {
|
||||
globalResults.classList.add('hidden');
|
||||
globalResults.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
globalDebounce = setTimeout(async () => {
|
||||
const resp = await fetch('/admin/global-search?q=' + encodeURIComponent(q));
|
||||
const hits = await resp.json();
|
||||
if (hits.length === 0) {
|
||||
globalResults.innerHTML = '<div class="px-4 py-3 text-xs text-gray-400">Aucun resultat.</div>';
|
||||
} else {
|
||||
globalResults.innerHTML = hits.map(h =>
|
||||
`<a href="${h.url}" class="flex items-center gap-3 px-4 py-2 hover:bg-white/80 border-b border-white/10 transition-colors">
|
||||
<span class="text-sm">${typeIcons[h.type] || ''}</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<span class="text-xs font-bold block truncate">${h.label}</span>
|
||||
${h.sub ? `<span class="text-[10px] text-gray-400 block truncate">${h.sub}</span>` : ''}
|
||||
</div>
|
||||
<span class="px-1.5 py-0.5 bg-gray-100 text-gray-500 font-bold uppercase text-[8px] rounded shrink-0">${typeLabels[h.type] || h.type}</span>
|
||||
</a>`
|
||||
).join('');
|
||||
}
|
||||
globalResults.classList.remove('hidden');
|
||||
}, 250);
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
/* istanbul ignore next */ if (!globalResults.contains(e.target) && e.target !== globalInput) {
|
||||
globalResults.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
globalInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') globalResults.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user