1/** 2 * DivisionFilter â Reusable division filter for list pages. 3 * 4 * Creates a SearchableDropdown with "All Divisions" + division options. 5 * Returns null if divisions are disabled for the company. 6 * 7 * Usage: 8 * const filter = await DivisionFilter.create({ onChange: (divisionId) => reload(divisionId) }); 9 * if (filter) container.prepend(filter.element); 10 * // On page destroy: filter?.destroy(); 11 */ 12 13import { companySettings } from '../services/company-settings.js'; 14import { divisionsApi } from '../api/divisions.js'; 15import { SearchableDropdown } from './SearchableDropdown.js'; 16 17// Session-level cache for the division list 18let _cachedDivisions = null; 19 20export const DivisionFilter = { 21 /** 22 * Create a division filter dropdown. 23 * @param {Object} options 24 * @param {Function} options.onChange - Called with divisionId (number|null) when selection changes 25 * @param {string} [options.label] - Label text (default: "Division") 26 * @returns {Promise<{element: HTMLElement, getValue: Function, destroy: Function}|null>} 27 */ 28 async create({ onChange, label = 'Division' } = {}) { 29 if (!companySettings.usesDivisions()) return null; 30 31 // Fetch divisions (cached per session) 32 if (!_cachedDivisions) { 33 try { 34 const response = await divisionsApi.getAll(); 35 _cachedDivisions = response?.divisions || []; 36 } catch (err) { 37 console.error('[DivisionFilter] Failed to load divisions:', err); 38 return null; 39 } 40 } 41 42 if (_cachedDivisions.length === 0) return null; 43 44 // Build wrapper with label + select 45 const wrapper = document.createElement('div'); 46 wrapper.className = 'division-filter-wrapper'; 47 48 const labelEl = document.createElement('label'); 49 labelEl.className = 'label-st text-xs'; 50 labelEl.textContent = label; 51 52 const select = document.createElement('select'); 53 select.className = 'form-input-st'; 54 select.setAttribute('data-searchable', ''); 55 select.setAttribute('data-searchable-size', 'small'); 56 select.setAttribute('aria-label', label); 57 58 // "All Divisions" option 59 const allOption = document.createElement('option'); 60 allOption.value = ''; 61 allOption.textContent = 'All Divisions'; 62 select.appendChild(allOption); 63 64 // Division options 65 for (const div of _cachedDivisions) { 66 const option = document.createElement('option'); 67 option.value = div.divisionId; 68 option.textContent = div.divisionTitle; 69 select.appendChild(option); 70 } 71 72 wrapper.appendChild(labelEl); 73 wrapper.appendChild(select); 74 75 // Enhance with SearchableDropdown 76 const instance = SearchableDropdown.enhance(select); 77 78 // Listen for changes 79 const changeHandler = () => { 80 const val = select.value; 81 const divisionId = val ? parseInt(val, 10) : null; 82 onChange?.(divisionId); 83 }; 84 select.addEventListener('change', changeHandler); 85 86 return { 87 element: wrapper, 88 getValue() { 89 const val = select.value; 90 return val ? parseInt(val, 10) : null; 91 }, 92 destroy() { 93 select.removeEventListener('change', changeHandler); 94 instance?.destroy(); 95 } 96 }; 97 }, 98 99 /** 100 * Clear the cached divisions (e.g., after admin changes divisions). 101 */ 102 clearCache() { 103 _cachedDivisions = null; 104 } 105};
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.