✨ feat(.env): Met à jour les URLs de signature et Stripe pour Ngrok ✨ feat(SignatureController): Ajoute le contrôleur de signature ✨ feat(DevisController): Intègre DocuSeal et la gestion des adresses client 🐛 fix(DevisManager.js): Corrige la sélection et la synchronisation des adresses ✨ feat(vich_uploader.yaml): Configure le stockage des fichiers PDF ✨ feat(initTomSelect.js): Améliore la gestion des prix des produits ✨ feat(DevisPdfService): Intègre la signature DocuSeal et améliore le pied de page ✨ feat(Client.php): Crée une soumission Docuseal pour les devis
94 lines
4.1 KiB
JavaScript
94 lines
4.1 KiB
JavaScript
// Cache pour éviter les requêtes HTTP répétitives
|
|
import TomSelect from "tom-select";
|
|
|
|
let productCache = null;
|
|
|
|
/**
|
|
* Initialise TomSelect sur un élément ou un groupe d'éléments
|
|
*/
|
|
export function initTomSelect(parent = document) {
|
|
parent.querySelectorAll('select').forEach((el) => {
|
|
if (el.tomselect) return;
|
|
|
|
// --- CONFIGURATION PRODUITS ---
|
|
if (el.getAttribute('data-load') === "product") {
|
|
const setupSelect = (data) => {
|
|
new TomSelect(el, {
|
|
valueField: 'id',
|
|
labelField: 'name',
|
|
searchField: 'name',
|
|
options: data,
|
|
maxOptions: null,
|
|
// LORSQU'ON SÉLECTIONNE UN PRODUIT
|
|
// Dans admin.js, section onChange de TomSelect :
|
|
onChange: (id) => {
|
|
if (!id) return;
|
|
|
|
// On s'assure de trouver le produit (id peut être string ou int)
|
|
const product = data.find(p => String(p.id) === String(id));
|
|
|
|
if (product) {
|
|
// On remonte au parent le plus proche (le bloc de ligne du devis)
|
|
const row = el.closest('.form-repeater__row') || el.closest('fieldset');
|
|
if (!row) return;
|
|
|
|
// Ciblage précis des inputs
|
|
const priceInput = row.querySelector('input[name*="[price_ht]"]');
|
|
const priceSupInput = row.querySelector('input[name*="[price_sup_ht]"]');
|
|
|
|
if (priceInput) {
|
|
priceInput.value = product.price1day;
|
|
// Indispensable pour que d'autres scripts (calcul totaux) voient le changement
|
|
priceInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
priceInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
|
|
if (priceSupInput) {
|
|
priceSupInput.value = product.priceSup;
|
|
priceSupInput.dispatchEvent(new Event('input', { bubbles: true }));
|
|
priceSupInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
}
|
|
},
|
|
render: {
|
|
option: (data, escape) => `
|
|
<div class="flex items-center gap-3 py-2 px-3 border-b border-slate-800/50">
|
|
<img src="${escape(data.image)}" class="w-8 h-8 object-cover rounded shadow-sm">
|
|
<div class="flex flex-col">
|
|
<div class="text-[13px] font-bold text-white">${escape(data.name)}</div>
|
|
<div class="text-[10px] text-slate-400">J1: ${escape(data.price1day)}€ | Sup: ${escape(data.priceSup)}€</div>
|
|
</div>
|
|
</div>`,
|
|
item: (data, escape) => `
|
|
<div class="text-blue-400 font-bold flex items-center gap-2">
|
|
<span class="w-1.5 h-1.5 rounded-full bg-blue-500"></span>
|
|
${escape(data.name)}
|
|
</div>`
|
|
}
|
|
});
|
|
};
|
|
|
|
// Utilisation du cache ou fetch
|
|
if (productCache) {
|
|
setupSelect(productCache);
|
|
} else {
|
|
fetch("/crm/products/json")
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
productCache = data;
|
|
setupSelect(data);
|
|
});
|
|
}
|
|
}
|
|
// --- AUTRES SELECTS ---
|
|
else {
|
|
new TomSelect(el, {
|
|
controlInput: null,
|
|
allowEmptyOption: true,
|
|
highlight: true,
|
|
plugins: ['dropdown_input'],
|
|
});
|
|
}
|
|
});
|
|
}
|