1/** 2 * Système de select personnalisés avec recherche intégrée 3 * Remplace les selects natifs par des composants custom avec filtrage 4 * Version avec icônes et dropdown 2 colonnes 5 */ 6 7// Icônes SVG pour les dispositifs 8const dispositifIcons = { 9 'ordinateur-portable': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M2 20h20"/><path d="M7 16v4M17 16v4"/></svg>`, 10 'aspirateur': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="18" r="3"/><path d="M11 18h6a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-4"/><path d="M8 6V4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v11"/></svg>`, 11 'telephone': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="2" width="14" height="20" rx="3"/><path d="M12 18h.01"/></svg>`, 12 'smartphone': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="2" width="14" height="20" rx="3"/><path d="M12 18h.01"/></svg>`, 13 'gps': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="10" r="3"/><path d="M12 2a8 8 0 0 0-8 8c0 5.4 8 12 8 12s8-6.6 8-12a8 8 0 0 0-8-8z"/></svg>`, 14 'collier_de_chien': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><ellipse cx="12" cy="12" rx="9" ry="4"/><path d="M12 16v3"/><circle cx="12" cy="20" r="1"/></svg>`, 15 'talkie_walkie': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="6" y="4" width="12" height="18" rx="2"/><path d="M10 2v2M14 2v2"/><rect x="8" y="7" width="8" height="4" rx="1"/><circle cx="12" cy="16" r="2"/></svg>`, 16 'medical': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 3h6v6h6v6h-6v6H9v-6H3V9h6z"/></svg>`, 17 'enceinte': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="2" width="14" height="20" rx="3"/><circle cx="12" cy="14" r="4"/><circle cx="12" cy="6" r="2"/></svg>`, 18 'alarme': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2a7 7 0 0 0-7 7c0 7 7 13 7 13s7-6 7-13a7 7 0 0 0-7-7z"/><circle cx="12" cy="9" r="2.5"/><path d="M2 2l2 2M22 2l-2 2"/></svg>`, 19 'tablette': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="2" width="16" height="20" rx="2"/><path d="M12 18h.01"/></svg>`, 20 'lecteur_code_barre': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 5v14"/><path d="M6 5v14"/><path d="M10 5v14"/><path d="M13 5v14"/><path d="M17 5v14"/><path d="M21 5v14"/><path d="M8 5v14" stroke-width="2.5"/><path d="M15 5v14" stroke-width="2.5"/></svg>`, 21 'montre': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="6"/><path d="M12 6V2M12 22v-4"/><path d="M9 2h6M9 22h6"/><path d="M12 9v3l2 2"/></svg>`, 22 'camescope': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="6" width="14" height="12" rx="2"/><path d="M16 10l6-4v12l-6-4z"/></svg>`, 23 'telecommande_grue': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="2" width="14" height="20" rx="2"/><circle cx="9" cy="8" r="1.5"/><circle cx="15" cy="8" r="1.5"/><rect x="8" y="13" width="8" height="6" rx="1"/></svg>`, 24 'telephone_sans_fil': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="7" y="4" width="10" height="18" rx="2"/><path d="M10 7h4"/><rect x="9" y="10" width="6" height="8" rx="1"/><path d="M12 2v2"/><path d="M8 2c-1 1-1.5 2-1.5 3M16 2c1 1 1.5 2 1.5 3"/></svg>`, 25 'drone': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="8" y="10" width="8" height="4" rx="1"/><circle cx="4" cy="6" r="2"/><circle cx="20" cy="6" r="2"/><circle cx="4" cy="18" r="2"/><circle cx="20" cy="18" r="2"/><path d="M6 7l2 3M18 7l-2 3M6 17l2-3M18 17l-2-3"/></svg>`, 26 'babyphone': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="6" y="8" width="12" height="14" rx="3"/><circle cx="12" cy="15" r="3"/><path d="M9 4a3 3 0 0 1 6 0"/><path d="M7 2a5 5 0 0 1 10 0"/></svg>`, 27 'tpe_terminal_paiement': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="5" y="2" width="14" height="20" rx="2"/><rect x="7" y="4" width="10" height="5" rx="1"/><path d="M8 12h2M11 12h2M14 12h2"/><path d="M8 15h2M11 15h2M14 15h2"/><path d="M8 18h8"/></svg>`, 28 'casque_sans_fil': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 14v-2a9 9 0 0 1 18 0v2"/><rect x="1" y="14" width="4" height="6" rx="1"/><rect x="19" y="14" width="4" height="6" rx="1"/></svg>`, 29 'cuisine': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2v4M12 18v4"/><rect x="4" y="6" width="16" height="12" rx="2"/><circle cx="8" cy="12" r="2"/><path d="M14 10h4M14 14h4"/></svg>`, 30 'default': `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/></svg>` 31}; 32 33// Données des dispositifs par type de produit 34const dispositifsByTypeProduit = { 35 'batterie': [ 36 { value: 'ordinateur-portable', label: 'Ordinateur portable' }, 37 { value: 'aspirateur', label: 'Aspirateur' }, 38 { value: 'telephone', label: 'Téléphone' }, 39 { value: 'gps', label: 'GPS' }, 40 { value: 'collier_de_chien', label: 'Collier de chien' }, 41 { value: 'talkie_walkie', label: 'Talkie-walkie' }, 42 { value: 'medical', label: 'Matériel médical' }, 43 { value: 'enceinte', label: 'Enceinte' }, 44 { value: 'alarme', label: 'Alarme' }, 45 { value: 'tablette', label: 'Tablette' }, 46 { value: 'lecteur_code_barre', label: 'Lecteur code barre' }, 47 { value: 'montre', label: 'Montre' }, 48 { value: 'camescope', label: 'Caméscope / Appareil photo' }, 49 { value: 'telecommande_grue', label: 'Télécommande grue' }, 50 { value: 'telephone_sans_fil', label: 'Téléphone sans fil' }, 51 { value: 'drone', label: 'Drone' }, 52 { value: 'babyphone', label: 'Babyphone' }, 53 { value: 'tpe_terminal_paiement', label: 'TPE / Terminal de paiement' }, 54 { value: 'casque_sans_fil', label: 'Casque sans fil' }, 55 { value: 'cuisine', label: 'Cuisine' } 56 ], 57 'chargeur': [ 58 { value: 'ordinateur-portable', label: 'Ordinateur portable' }, 59 { value: 'telephone', label: 'Téléphone' }, 60 { value: 'smartphone', label: 'Smartphone' }, 61 { value: 'tablette', label: 'Tablette' }, 62 { value: 'camescope', label: 'Caméscope / Appareil photo' } 63 ] 64}; 65 66/**
67 * Les marques sont calculées manuellement pour chaque "dispositifsByTypeProduit" 68 * dans le script /ajax/generer_marques.php 69 */ 70 71// Vérification des marques 72if (typeof marquesBatterieByDispositif === 'undefined' || typeof marquesChargeurByDispositif === 'undefined') { 73 console.error('Les données de marques ne sont pas chargées.'); 74} 75 76// Ãtat global de l'application 77const appState = { 78 typeProduit: '', 79 dispositif: '', 80 marque: '', 81 modele: '', 82 modelesList: [] 83}; 84 85// Classe pour gérer un select personnalisé 86class CustomSelect { 87 constructor(id, options = {}) { 88 this.id = id; 89 this.container = document.getElementById(id); 90 this.name = options.name || id; 91 this.placeholder = options.placeholder || 'Sélectionner...'; 92 this.searchable = options.searchable || false; 93 this.disabled = options.disabled || false; 94 this.onChange = options.onChange || null; 95 this.showOtherButton = options.showOtherButton || false; 96 this.otherButtonLabel = options.otherButtonLabel || 'Autre'; 97 this.onOtherClick = options.onOtherClick || null; 98 this.showIcons = options.showIcons || false; // Nouvelle option pour les icônes 99 this.multiColumn = options.multiColumn || false; // Nouvelle option pour 2 colonnes 100 this.items = []; 101 this.selectedValue = ''; 102 this.selectedLabel = ''; 103 this.isOpen = false; 104 this.filteredItems = []; 105 106 this.init(); 107 } 108 109 init() { 110 this.render(); 111 this.attachEvents(); 112 } 113 114 render() { 115 const searchHTML = this.searchable ? ` 116 <div class="custom-select__search"> 117 <input 118 type="text" 119 class="custom-select__search-input" 120 placeholder="Rechercher..." 121 autocomplete="off" 122 /> 123 <svg class="custom-select__search-icon" width="16" height="16" viewBox="0 0 16 16" fill="none"> 124 <path d="M7 12C9.76142 12 12 9.76142 12 7C12 4.23858 9.76142 2 7 2C4.23858 2 2 4.23858 2 7C2 9.76142 4.23858 12 7 12Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 125 <path d="M10.5 10.5L14 14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 126 </svg> 127 </div> 128 ` : ''; 129 130 // Ajouter la classe multi-column si nécessaire 131 const multiColumnClass = this.multiColumn ? 'custom-select--multi-column' : ''; 132 const iconsClass = this.showIcons ? 'custom-select--with-icons' : ''; 133 134 this.container.innerHTML = ` 135 <div class="custom-select ${this.disabled ? 'custom-select--disabled' : ''} ${multiColumnClass} ${iconsClass}" data-name="${this.name}"> 136 <input type="hidden" name="${this.name}" value="${this.selectedValue}" /> 137 <button type="button" class="custom-select__trigger" ${this.disabled ? 'disabled' : ''}> 138 <span class="custom-select__value">${this.selectedLabel || this.placeholder}</span> 139 <svg class="custom-select__arrow" width="12" height="12" viewBox="0 0 12 12" fill="none"> 140 <path d="M2 4L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 141 </svg> 142 </button> 143 <div class="custom-select__dropdown"> 144 ${searchHTML} 145 <div class="custom-select__options"> 146 <div class="custom-select__option custom-select__option--placeholder"> 147 ${this.placeholder} 148 </div> 149 </div> 150 </div> 151 </div> 152 `; 153 154 this.selectElement = this.container.querySelector('.custom-select'); 155 this.trigger = this.container.querySelector('.custom-select__trigger'); 156 this.valueDisplay = this.container.querySelector('.custom-select__value'); 157 this.dropdown = this.container.querySelector('.custom-select__dropdown'); 158 this.optionsContainer = this.container.querySelector('.custom-select__options'); 159 this.hiddenInput = this.container.querySelector('input[type="hidden"]'); 160 161 if (this.searchable) { 162 this.searchInput = this.container.querySelector('.custom-select__search-input'); 163 } 164 } 165 166 attachEvents() { 167 this.trigger.addEventListener('click', (e) => { 168 e.stopPropagation(); 169 this.toggle(); 170 }); 171 172 if (this.searchable && this.searchInput) { 173 this.searchInput.addEventListener('input', (e) => { 174 this.filterOptions(e.target.value); 175 }); 176 177 this.searchInput.addEventListener('click', (e) => { 178 e.stopPropagation(); 179 }); 180 } 181 182 document.addEventListener('click', (e) => { 183 if (!this.container.contains(e.target)) { 184 this.close(); 185 } 186 }); 187 188 this.trigger.addEventListener('keydown', (e) => { 189 if (e.key === 'Enter' || e.key === ' ') { 190 e.preventDefault(); 191 this.toggle(); 192 } else if (e.key === 'Escape') { 193 this.close(); 194 } 195 }); 196 } 197 198 toggle() { 199 if (this.disabled) return; 200 this.isOpen ? this.close() : this.open(); 201 } 202 203 open() { 204 if (this.disabled) return; 205 206 document.querySelectorAll('.custom-select').forEach(select => { 207 if (select !== this.selectElement) { 208 select.classList.remove('custom-select--open'); 209 } 210 }); 211 212 this.selectElement.classList.add('custom-select--open'); 213 this.isOpen = true; 214 215 // Ajuster la position du dropdown pour éviter le débordement 216 this.adjustDropdownPosition(); 217 218 if (this.searchable && this.searchInput) { 219 setTimeout(() => { 220 this.searchInput.focus(); 221 }, 100); 222 } 223 } 224 225 /** 226 * Ajuste la position du dropdown pour qu'il ne déborde pas de l'écran 227 */ 228 adjustDropdownPosition() { 229 if (!this.multiColumn) return; 230 231 // Reset des classes de position 232 this.selectElement.classList.remove('custom-select--align-right', 'custom-select--align-left'); 233 this.dropdown.style.left = ''; 234 this.dropdown.style.right = ''; 235 236 // Attendre que le dropdown soit rendu 237 requestAnimationFrame(() => { 238 const triggerRect = this.trigger.getBoundingClientRect(); 239 const dropdownRect = this.dropdown.getBoundingClientRect(); 240 const viewportWidth = window.innerWidth; 241 const margin = 20; // Marge de sécurité 242 243 // Vérifier si le dropdown déborde à droite 244 if (triggerRect.left + dropdownRect.width > viewportWidth - margin) { 245 // Aligner à droite du trigger 246 this.dropdown.style.left = 'auto'; 247 this.dropdown.style.right = '0'; 248 249 // Si ça déborde encore, décaler vers la gauche 250 const newRight = viewportWidth - triggerRect.right; 251 if (newRight + dropdownRect.width > viewportWidth - margin) { 252 const offset = (triggerRect.left + dropdownRect.width) - (viewportWidth - margin); 253 this.dropdown.style.right = 'auto'; 254 this.dropdown.style.left = `-${offset}px`; 255 } 256 } else { 257 // Aligner à gauche normalement 258 this.dropdown.style.left = '0'; 259 this.dropdown.style.right = 'auto'; 260 } 261 }); 262 } 263 264 close() { 265 this.selectElement.classList.remove('custom-select--open'); 266 this.isOpen = false; 267 268 if (this.searchable && this.searchInput) { 269 this.searchInput.value = ''; 270 this.filterOptions(''); 271 } 272 } 273 274 setItems(items) { 275 this.items = items; 276 this.filteredItems = [...items]; 277 this.renderOptions(); 278 } 279 280 filterOptions(searchTerm) { 281 const term = searchTerm.toLowerCase().trim(); 282 283 if (!term) { 284 this.filteredItems = [...this.items]; 285 } else { 286 this.filteredItems = this.items.filter(item => 287 item.label.toLowerCase().includes(term) 288 ); 289 } 290 291 this.renderOptions(); 292 } 293 294 getIcon(value) {
295 return dispositifIcons[value] || dispositifIcons['default']; 296 } 297 298 /** 299 * Formate le texte avec une majuscule initiale et le reste en minuscule 300 * @param {string} text - Le texte à formater 301 * @returns {string} - Le texte formaté 302 */ 303 formatLabel(text) { 304 if (!text) return text; 305 return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(); 306 } 307 308 renderOptions() { 309 let optionsHTML = ''; 310 311 if (this.filteredItems.length === 0) { 312 optionsHTML = ` 313 <div class="custom-select__option custom-select__option--empty"> 314 Le résultat n'apparait pas ici ? Cliquez sur "Autre" pour voir la liste complète. 315 </div> 316 `; 317 } else { 318 optionsHTML = this.filteredItems.map(item => { 319 const iconHTML = this.showIcons 320 ? `<span class="custom-select__option-icon">${this.getIcon(item.value)}</span>` 321 : ''; 322 323 const displayLabel = this.formatLabel(item.label); 324 325 return ` 326 <div class="custom-select__option ${item.value === this.selectedValue ? 'custom-select__option--selected' : ''}" 327 data-value="${item.value}"> 328 ${iconHTML} 329 <span class="custom-select__option-label">${displayLabel}</span> 330 </div> 331 `; 332 }).join(''); 333 } 334 335 // Toujours afficher le bouton "Autre" s'il est activé 336 if (this.showOtherButton) { 337 const otherIcon = this.showIcons 338 ? `<span class="custom-select__option-icon"> 339 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> 340 <circle cx="12" cy="12" r="10"/> 341 <path d="M12 8v8M8 12h8"/> 342 </svg> 343 </span>` 344 : ''; 345 346 optionsHTML += ` 347 <div class="custom-select__option custom-select__option--other" data-action="other"> 348 ${otherIcon} 349 <span class="custom-select__option-label">${this.otherButtonLabel}</span> 350 </div> 351 `; 352 } 353 354 this.optionsContainer.innerHTML = optionsHTML; 355 356 this.optionsContainer.querySelectorAll('.custom-select__option').forEach(option => { 357 option.addEventListener('click', (e) => { 358 const target = e.target.closest('.custom-select__option'); 359 const value = target.dataset.value; 360 const action = target.dataset.action; 361 362 if (action === 'other') { 363 if (this.onOtherClick) { 364 this.onOtherClick(); 365 } 366 this.close(); 367 } else if (value) { 368 this.selectOption(value); 369 } 370 }); 371 }); 372 } 373 374 selectOption(value) { 375 const item = this.items.find(i => i.value === value); 376 if (!item) return; 377 378 this.selectedValue = value; 379 this.selectedLabel = item.label; 380 381 const displayLabel = this.formatLabel(item.label); 382 383 // Afficher l'icône dans le trigger si showIcons est activé 384 if (this.showIcons) { 385 this.valueDisplay.innerHTML = ` 386 <span class="custom-select__value-icon">${this.getIcon(value)}</span> 387 <span>${displayLabel}</span> 388 `; 389 } else { 390 this.valueDisplay.textContent = displayLabel; 391 } 392 393 this.hiddenInput.value = value; 394 395 this.optionsContainer.querySelectorAll('.custom-select__option').forEach(opt => { 396 opt.classList.toggle('custom-select__option--selected', opt.dataset.value === value); 397 }); 398 399 this.close(); 400 401 if (this.onChange) { 402 this.onChange(value, item); 403 } 404 } 405 406 reset(placeholder) { 407 this.selectedValue = ''; 408 this.selectedLabel = ''; 409 this.valueDisplay.textContent = placeholder || this.placeholder; 410 this.hiddenInput.value = ''; 411 this.items = []; 412 this.filteredItems = []; 413 this.renderOptions(); 414 } 415 416 setDisabled(disabled) { 417 this.disabled = disabled; 418 this.trigger.disabled = disabled; 419 this.selectElement.classList.toggle('custom-select--disabled', disabled); 420 if (disabled) { 421 this.close(); 422 } 423 } 424 425 getValue() { 426 return this.selectedValue; 427 } 428} 429 430// Instances des selects personnalisés 431let selectTypeProduit, selectDispositif, selectMarque, selectModele; 432 433// Initialisation 434document.addEventListener('DOMContentLoaded', function() { 435 // Type de produit - simple, 1 colonne 436 selectTypeProduit = new CustomSelect('select_type_produit', { 437 name: 'type_produit', 438 placeholder: '-- Sélectionnez un type --', 439 searchable: false, 440 onChange: handleTypeProduitChange 441 });
442 443 // Dispositif - avec icônes et 2 colonnes 444 selectDispositif = new CustomSelect('select_dispositif', { 445 name: 'dispositif', 446 placeholder: '-- Choisissez d\'abord un type --', 447 searchable: false, 448 disabled: true, 449 showOtherButton: true, 450 otherButtonLabel: 'Autre', 451 onOtherClick: handleDispositifOther, 452 onChange: handleDispositifChange, 453 showIcons: true, // Active les icônes 454 multiColumn: true // Active le mode 2 colonnes 455 }); 456 457 // Marque - 2 colonnes, sans icônes 458 selectMarque = new CustomSelect('select_marque', { 459 name: 'marque', 460 placeholder: '-- Choisissez d\'abord un dispositif --', 461 searchable: true, 462 disabled: true, 463 showOtherButton: true, 464 otherButtonLabel: 'Autre', 465 onOtherClick: handleMarqueOther, 466 onChange: handleMarqueChange, 467 multiColumn: true // Active le mode 2 colonnes 468 }); 469 470 // Modèle - 2 colonnes, sans icônes 471 selectModele = new CustomSelect('select_modele', { 472 name: 'id_modele', 473 placeholder: '-- Choisissez d\'abord une marque --', 474 searchable: true, 475 disabled: true, 476 showOtherButton: true, 477 otherButtonLabel: 'Autre', 478 onOtherClick: handleModeleOther, 479 onChange: handleModeleChange, 480 multiColumn: true // Active le mode 2 colonnes 481 }); 482 483 // Charger les types de produits 484 selectTypeProduit.setItems([ 485 { value: 'batterie', label: 'Batterie' }, 486 { value: 'chargeur', label: 'Chargeur' } 487 ]); 488}); 489 490/** 491 * Gère le clic sur "Autre" pour le dispositif 492 */ 493function handleDispositifOther() { 494 const typeProduit = appState.typeProduit; 495 496 if (typeProduit === 'batterie') { 497 window.location.href = '/batterie.html'; 498 } else if (typeProduit === 'chargeur') { 499 window.location.href = '/chargeur.html'; 500 } 501} 502 503/** 504 * Gère le clic sur "Autre" pour la marque 505 */ 506function handleMarqueOther() { 507 const typeProduit = appState.typeProduit; 508 const dispositif = appState.dispositif; 509 const marque = appState.marque; 510 511 if (typeProduit && dispositif && marque) { 512 const dispositifSlug = dispositif.replace(/_/g, '-'); 513 const marqueParts = marque.split('_'); 514 const marqueSlug = marqueParts[marqueParts.length - 1].toLowerCase(); 515 window.location.href = `/${typeProduit}/${dispositifSlug}/${marqueSlug}.html`; 516 } else if (typeProduit && dispositif) { 517 const dispositifSlug = dispositif.replace(/_/g, '-'); 518 window.location.href = `/${typeProduit}/${dispositifSlug}.html`; 519 } 520} 521 522/** 523 * Gère le clic sur "Autre" pour le modèle 524 */ 525function handleModeleOther() { 526 const typeProduit = appState.typeProduit; 527 const dispositif = appState.dispositif; 528 const marque = appState.marque; 529 530 if (typeProduit && dispositif && marque) { 531 const dispositifSlug = dispositif.replace(/_/g, '-'); 532 const marqueParts = marque.split('_'); 533 const marqueSlug = marqueParts[marqueParts.length - 1].toLowerCase(); 534 window.location.href = `/${typeProduit}/${dispositifSlug}/${marqueSlug}.html`; 535 } 536} 537 538/** 539 * Gère le changement de type de produit 540 */ 541function handleTypeProduitChange(value) { 542 appState.typeProduit = value; 543 544 selectDispositif.reset('-- Sélectionnez un dispositif --'); 545 selectMarque.reset('-- Choisissez d\'abord un dispositif --'); 546 selectModele.reset('-- Choisissez d\'abord une marque --'); 547 548 selectMarque.setDisabled(true); 549 selectModele.setDisabled(true); 550 551 if (!value) { 552 selectDispositif.setDisabled(true); 553 return; 554 } 555 556 const dispositifs = dispositifsByTypeProduit[value] || []; 557 558 if (dispositifs.length > 0) { 559 selectDispositif.setItems(dispositifs); 560 selectDispositif.setDisabled(false); 561 } else { 562 selectDispositif.setDisabled(true); 563 } 564} 565 566/** 567 * Gère le changement de dispositif 568 */ 569function handleDispositifChange(value) { 570 appState.dispositif = value; 571 572 selectMarque.reset('-- Sélectionnez une marque --'); 573 selectModele.reset('-- Choisissez d\'abord une marque --'); 574 selectModele.setDisabled(true); 575 576 if (!value) { 577 selectMarque.setDisabled(true); 578 return; 579 } 580 581 const marquesDict = appState.typeProduit === 'chargeur' 582 ? marquesChargeurByDispositif 583 : marquesBatterieByDispositif; 584 585 const marques = marquesDict[value] || []; 586 587 if (marques.length > 0) { 588 selectMarque.setItems(marques); 589 selectMarque.setDisabled(false); 590 } else { 591 selectMarque.setDisabled(true); 592 console.warn(`Aucune marque trouvée pour ${appState.typeProduit} / ${value}`); 593 } 594} 595 596/** 597 * Gère le changement de marque 598 */ 599function handleMarqueChange(value) { 600 appState.marque = value; 601 602 selectModele.reset('-- Chargement des modèles... --'); 603 selectModele.setDisabled(true); 604 605 if (!value) { 606 selectModele.reset('-- Choisissez d\'abord une marque --'); 607 return; 608 } 609 610 fetchModeles(value); 611} 612 613/** 614 * Gère le changement de modèle 615 */
616function handleModeleChange(value) { 617 appState.modele = value; 618 619 if (value) { 620 window.location.href = value; 621 } 622} 623 624/** 625 * Récupère les modèles depuis le serveur 626 */ 627function fetchModeles(marque) { 628 const lang = 'fr'; 629 const url = `/ajax/get_modeles.php?type_article=${encodeURIComponent(marque)}&lang=${lang}`; 630 631 fetch(url) 632 .then(response => { 633 if (!response.ok) { 634 throw new Error(`HTTP error! status: ${response.status}`); 635 } 636 return response.json(); 637 }) 638 .then(data => { 639 if (data.success && data.modeles && data.modeles.length > 0) { 640 appState.modelesList = data.modeles; 641 selectModele.setItems(data.modeles); 642 selectModele.setDisabled(false); 643 } else { 644 selectModele.reset('-- Aucun modèle disponible --'); 645 selectModele.setDisabled(true); 646 console.warn(`Aucun modèle trouvé pour ${marque}`); 647 } 648 }) 649 .catch(error => { 650 console.error('Erreur lors de la récupération des modèles:', error); 651 selectModele.reset('-- Erreur de chargement --'); 652 selectModele.setDisabled(true); 653 }); 654}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.