feat: pages services NDD/Esy-Web + index Meilisearch + sync + recherche

Pages services :
- /admin/services/ndd : liste tous les NDD avec client, registrar,
  Cloudflare, gestion, facturation, expiration + barre recherche
- /admin/services/esyweb : liste tous les sites avec client, UUID,
  type, statut + barre recherche
- Liens sidebar mis à jour (Esy-Web → esyweb, Nom de domaine → ndd)

MeilisearchService :
- Index customer_ndd : searchable fqdn/registrar/customerName/customerEmail,
  filterable customerId/isGestion/isBilling
- Index customer_website : searchable name/uuid/customerName/customerEmail,
  filterable customerId/type/state
- CRUD : indexDomain/removeDomain/searchDomains, indexWebsite/removeWebsite/searchWebsites
- Serializers avec infos client intégrées (customerName, customerEmail, customerId)

SyncController :
- Route POST /admin/sync/domains : sync tous les Domain dans Meilisearch
- Route POST /admin/sync/websites : sync tous les Website dans Meilisearch
- Compteurs totalDomains et totalWebsites dans index

Template admin/sync :
- Bloc "Noms de domaine" (slate) avec bouton sync
- Bloc "Sites Internet" (blue) avec bouton sync

Recherche (app.js) :
- renderHit adapté : affiche fqdn/name pour NDD/sites, customerName en sous-texte
- Lien vers la fiche client (customerId) pour les résultats NDD/Website
- setupSearch configuré pour search-ndd et search-websites

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-04-04 21:26:17 +02:00
parent 9316743ac6
commit f68712bd02
8 changed files with 388 additions and 6 deletions

View File

@@ -142,12 +142,16 @@ document.addEventListener('DOMContentLoaded', () => {
});
// Search (customers & revendeurs)
const renderHit = (h, linkPrefix) =>
`<a href="${linkPrefix}${h.id}" class="block px-4 py-2 hover:bg-gray-50 border-b border-gray-100 text-xs">
<span class="font-bold">${h.fullName || h.raisonSociale || (h.firstName + ' ' + h.lastName)}</span>
${h.email ? `<span class="text-gray-400 ml-2">${h.email}</span>` : ''}
const renderHit = (h, linkPrefix) => {
const id = h.customerId || h.id;
const name = h.fqdn || h.name || h.fullName || h.raisonSociale || (h.firstName + ' ' + h.lastName);
const sub = h.customerName ? `<span class="text-gray-400 ml-2">${h.customerName}</span>` : (h.email ? `<span class="text-gray-400 ml-2">${h.email}</span>` : '');
return `<a href="${linkPrefix}${id}" class="block px-4 py-2 hover:bg-gray-50 border-b border-gray-100 text-xs">
<span class="font-bold">${name}</span>
${sub}
${h.codeRevendeur ? `<span class="ml-2 px-1 py-0.5 bg-gray-900 text-[#fabf04] text-[9px] font-bold">${h.codeRevendeur}</span>` : ''}
</a>`;
};
const performSearch = async (searchUrl, linkPrefix, results, q) => {
const resp = await fetch(`${searchUrl}?q=${encodeURIComponent(q)}`);
@@ -184,6 +188,8 @@ document.addEventListener('DOMContentLoaded', () => {
setupSearch('search-customers', 'search-results', '/admin/clients/search', '/admin/clients/');
setupSearch('search-revendeurs', 'search-results-revendeurs', '/admin/revendeurs/search', '/admin/revendeurs/');
setupSearch('search-ndd', 'search-ndd-results', '/admin/services/ndd/search', '/admin/clients/');
setupSearch('search-websites', 'search-websites-results', '/admin/services/esyweb/search', '/admin/clients/');
// Tarif tabs
const tarifTabs = document.getElementById('tarif-tabs');