Files
ludikevent_crm/assets/libs/initTomSelect.js
Serreau Jovann 35e24491f4 ```text
 feat(crm): Améliore l'interface et la recherche de produits/options

Ce commit modernise l'interface utilisateur pour la recherche et la sélection de produits et d'options. Il améliore l'apparence
visuelle, l'ergonomie et la réactivité, en utilisant des composants plus modernes et des animations plus fluides. Les
fonctionnalités de recherche ont été optimisées pour une meilleure expérience utilisateur. Ajout de nouvelles classes
'SearchProductDevis' et 'SearchOptionsDevis' pour la gestion des options dans Devis.
```
2026-01-29 18:12:06 +01:00

120 lines
5.0 KiB
JavaScript

import TomSelect from "tom-select";
// Cache séparé pour éviter les conflits entre produits et options
let productCache = null;
let optionsCache = null;
/**
* Initialise TomSelect sur un élément ou un groupe d'éléments
* Adapté pour le thème sombre forcé.
*/
export function initTomSelect(parent = document) {
parent.querySelectorAll('select').forEach((el) => {
// --- CLAUSES DE GARDE ---
if (el.tomselect || el.hasAttribute('ds')) return;
// --- CONFIGURATION COMMUNE RENDU ---
const commonRender = {
option: (data, escape) => `
<div class="flex items-center gap-3 py-3 px-4 border-b border-slate-700/50 hover:bg-slate-800 transition-colors">
<img src="${escape(data.image)}" class="w-10 h-10 object-cover rounded-lg shadow-lg border border-slate-700">
<div class="flex flex-col">
<div class="text-[13px] font-bold text-slate-100">${escape(data.name)}</div>
<div class="text-[10px] text-blue-500 font-bold uppercase tracking-wider">
${data.price1day ? `J1: ${escape(data.price1day)}€ | Sup: ${escape(data.priceSup)}` : `${escape(data.price)}`}
</div>
</div>
</div>`,
item: (data, escape) => `
<div class="text-blue-400 font-bold flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-blue-500 shadow-[0_0_8px_rgba(59,130,246,0.5)]"></span>
${escape(data.name)}
</div>`
};
// --- CONFIGURATION PRODUITS ---
if (el.getAttribute('data-load') === "product") {
const setupProduct = (data) => {
new TomSelect(el, {
valueField: 'id',
labelField: 'name',
searchField: 'name',
options: data,
maxOptions: null,
onChange: (id) => {
if (!id) return;
const product = data.find(p => String(p.id) === String(id));
if (product) {
const row = el.closest('.form-repeater__row') || el.closest('fieldset');
if (!row) return;
const priceInput = row.querySelector('input[name*="[price_ht]"]');
const priceSupInput = row.querySelector('input[name*="[price_sup_ht]"]');
if (priceInput) {
priceInput.value = product.price1day;
priceInput.dispatchEvent(new Event('input', { bubbles: true }));
}
if (priceSupInput) {
priceSupInput.value = product.priceSup;
priceSupInput.dispatchEvent(new Event('input', { bubbles: true }));
}
}
},
render: commonRender
});
};
if (productCache) setupProduct(productCache);
else {
fetch("/crm/products/json")
.then(r => r.json())
.then(data => { productCache = data; setupProduct(data); });
}
}
// --- CONFIGURATION OPTIONS ---
else if (el.getAttribute('data-load') === "options") {
const setupOptions = (data) => {
new TomSelect(el, {
valueField: 'id',
labelField: 'name',
searchField: 'name',
options: data,
maxOptions: null,
onChange: (id) => {
if (!id) return;
const product = data.find(p => String(p.id) === String(id));
const row = el.closest('.form-repeater__row') || el.closest('fieldset');
if (!row) return;
const priceInput = row.querySelector('input[name*="[price_ht]"]');
if (priceInput) {
priceInput.value = product.price;
priceInput.dispatchEvent(new Event('input', { bubbles: true }));
}
},
render: commonRender
});
};
if (optionsCache) setupOptions(optionsCache);
else {
fetch("/crm/options/json")
.then(r => r.json())
.then(data => { optionsCache = data; setupOptions(data); });
}
}
// --- AUTRES SELECTS STANDARDS ---
else {
new TomSelect(el, {
controlInput: null,
allowEmptyOption: true,
highlight: true,
plugins: ['dropdown_input'],
});
}
});
}