PageSourceSearch

https://smrtbld.ai/js/components/DivisionFilter.js

js smrtbld.ai collected 2026-10-05 15:10:50 UTC 3,639 bytes, 105 lines download raw bytes

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.