|
7 | 7 | <form method="get" class="form-inline" style="margin-bottom: 1rem;"> |
8 | 8 | <div> |
9 | 9 | <label for="id_groups">{% trans "Groups" %}</label><br/> |
| 10 | + <input type="text" id="id_groups_filter" placeholder="{% trans 'Filter' %}" style="min-width: 360px; margin-bottom: .5rem;" /> |
| 11 | + <br/> |
10 | 12 | <select id="id_groups" name="groups" multiple size="12" style="min-width: 360px;"> |
11 | 13 | {% for g in groups %} |
12 | 14 | <option value="{{ g.id }}" {% if g.id in selected_group_ids %}selected{% endif %}>{{ g.name }}</option> |
|
45 | 47 | <th>{% trans "Name" %}</th> |
46 | 48 | <th>{% trans "Organization" %}</th> |
47 | 49 | <th>{% trans "Active" %}</th> |
48 | | - <th>{% trans "Staff status" %}</th> |
| 50 | + <th>{% trans "Staff" %}</th> |
| 51 | + <th>{% trans "Superuser" %}</th> |
49 | 52 | </tr> |
50 | 53 | </thead> |
51 | 54 | <tbody> |
|
59 | 62 | <!-- yesno:"+,," -> '+' for True, '' for False/None --> |
60 | 63 | <td class="bool-col">{{ u.is_active|yesno:"+,," }}</td> |
61 | 64 | <td class="bool-col">{{ u.is_staff|yesno:"+,," }}</td> |
| 65 | + <td class="bool-col">{{ u.is_superuser|yesno:"+,," }}</td> |
62 | 66 | </tr> |
63 | 67 | {% empty %} |
64 | 68 | <tr><td colspan="7">{% trans "No users match the current filters." %}</td></tr> |
|
70 | 74 | <p>{% trans "Select one or more Groups to view users. Optionally include superusers." %}</p> |
71 | 75 | {% endif %} |
72 | 76 | </div> |
| 77 | + |
| 78 | +<script> |
| 79 | + (function() { |
| 80 | + const filterInput = document.getElementById('id_groups_filter'); |
| 81 | + const selectEl = document.getElementById('id_groups'); |
| 82 | + if (!filterInput || !selectEl) return; |
| 83 | + |
| 84 | + // Snapshot original options once |
| 85 | + const allOptions = Array.from(selectEl.options).map(opt => ({ |
| 86 | + value: opt.value, |
| 87 | + label: opt.text, |
| 88 | + textLower: opt.text.toLowerCase(), |
| 89 | + selected: opt.selected, |
| 90 | + })); |
| 91 | + |
| 92 | + function syncSelectedState() { |
| 93 | + const selectedValues = new Set(Array.from(selectEl.selectedOptions).map(o => o.value)); |
| 94 | + for (const o of allOptions) { |
| 95 | + o.selected = selectedValues.has(o.value); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + function renderOptions(query) { |
| 100 | + const q = (query || '').toLowerCase(); |
| 101 | + const filtered = allOptions.filter(o => o.selected || o.textLower.includes(q)); |
| 102 | + // Rebuild options while preserving selection |
| 103 | + selectEl.innerHTML = ''; |
| 104 | + for (const o of filtered) { |
| 105 | + const opt = document.createElement('option'); |
| 106 | + opt.value = o.value; |
| 107 | + opt.text = o.label; |
| 108 | + if (o.selected) opt.selected = true; |
| 109 | + selectEl.appendChild(opt); |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + selectEl.addEventListener('change', () => { |
| 114 | + syncSelectedState(); |
| 115 | + renderOptions(filterInput.value); |
| 116 | + }); |
| 117 | + |
| 118 | + filterInput.addEventListener('input', () => { |
| 119 | + syncSelectedState(); |
| 120 | + renderOptions(filterInput.value); |
| 121 | + }); |
| 122 | + |
| 123 | + // Initial render (no filter) |
| 124 | + renderOptions(''); |
| 125 | + })(); |
| 126 | +</script> |
73 | 127 | {% endblock %} |
0 commit comments