133 lines
6.1 KiB
HTML
133 lines
6.1 KiB
HTML
{#
|
|
Unit Picker Component
|
|
A reusable HTMX-based autocomplete for selecting seismographs/SLMs.
|
|
|
|
Usage: include "partials/unit_picker.html" with context
|
|
|
|
Variables available in context:
|
|
- selected_unit_id: Pre-selected unit ID (optional)
|
|
- selected_unit_display: Display text for pre-selected unit (optional)
|
|
- input_name: Name attribute for the hidden input (default: "deployed_with_unit_id")
|
|
- picker_id: Unique ID suffix for multiple pickers on same page (default: "")
|
|
- device_type_filter: Filter by device type: "seismograph", "slm", or empty for all (default: "")
|
|
- deployed_only: Only show deployed units (default: false)
|
|
#}
|
|
|
|
{% set picker_id = picker_id|default("") %}
|
|
{% set input_name = input_name|default("deployed_with_unit_id") %}
|
|
{% set selected_unit_id = selected_unit_id|default("") %}
|
|
{% set selected_unit_display = selected_unit_display|default("") %}
|
|
{% set device_type_filter = device_type_filter|default("") %}
|
|
{% set deployed_only = deployed_only|default(false) %}
|
|
|
|
<div class="unit-picker relative" id="unit-picker-container{{ picker_id }}">
|
|
<!-- Hidden input for form submission (stores unit ID) -->
|
|
<input type="hidden"
|
|
name="{{ input_name }}"
|
|
id="unit-picker-value{{ picker_id }}"
|
|
value="{{ selected_unit_id }}">
|
|
|
|
<!-- Search Input -->
|
|
<div class="relative">
|
|
<input type="text"
|
|
id="unit-picker-search{{ picker_id }}"
|
|
placeholder="Search by unit ID or note..."
|
|
class="w-full px-4 py-2 pr-10 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange focus:border-seismo-orange"
|
|
autocomplete="off"
|
|
value="{{ selected_unit_display }}"
|
|
hx-get="/api/roster/search/units"
|
|
hx-trigger="keyup changed delay:300ms, focus"
|
|
hx-target="#unit-picker-dropdown{{ picker_id }}"
|
|
hx-vals='{"picker_id": "{{ picker_id }}", "device_type": "{{ device_type_filter }}", "deployed_only": "{{ deployed_only|lower }}"}'
|
|
name="q"
|
|
onfocus="showUnitDropdown('{{ picker_id }}')"
|
|
oninput="handleUnitSearchInput('{{ picker_id }}', this.value)">
|
|
|
|
<!-- Search icon -->
|
|
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
|
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Clear button (shown when unit is selected) -->
|
|
<button type="button"
|
|
id="unit-picker-clear{{ picker_id }}"
|
|
class="absolute inset-y-0 right-8 flex items-center pr-1 {{ 'hidden' if not selected_unit_id else '' }}"
|
|
onclick="clearUnitSelection('{{ picker_id }}')"
|
|
title="Clear selection">
|
|
<svg class="w-4 h-4 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Dropdown Results Container -->
|
|
<div id="unit-picker-dropdown{{ picker_id }}"
|
|
class="hidden absolute z-50 w-full mt-1 bg-white dark:bg-slate-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-60 overflow-y-auto">
|
|
<!-- Results loaded via HTMX -->
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
{# Unit picker functions - defined once, work for any picker_id #}
|
|
if (typeof selectUnit === 'undefined') {
|
|
function selectUnit(unitId, displayText, pickerId = '') {
|
|
const valueInput = document.getElementById('unit-picker-value' + pickerId);
|
|
const searchInput = document.getElementById('unit-picker-search' + pickerId);
|
|
const dropdown = document.getElementById('unit-picker-dropdown' + pickerId);
|
|
const clearBtn = document.getElementById('unit-picker-clear' + pickerId);
|
|
|
|
if (valueInput) valueInput.value = unitId;
|
|
if (searchInput) searchInput.value = displayText;
|
|
if (dropdown) dropdown.classList.add('hidden');
|
|
if (clearBtn) clearBtn.classList.remove('hidden');
|
|
}
|
|
|
|
function clearUnitSelection(pickerId = '') {
|
|
const valueInput = document.getElementById('unit-picker-value' + pickerId);
|
|
const searchInput = document.getElementById('unit-picker-search' + pickerId);
|
|
const clearBtn = document.getElementById('unit-picker-clear' + pickerId);
|
|
|
|
if (valueInput) valueInput.value = '';
|
|
if (searchInput) {
|
|
searchInput.value = '';
|
|
searchInput.focus();
|
|
}
|
|
if (clearBtn) clearBtn.classList.add('hidden');
|
|
}
|
|
|
|
function showUnitDropdown(pickerId = '') {
|
|
const dropdown = document.getElementById('unit-picker-dropdown' + pickerId);
|
|
if (dropdown) dropdown.classList.remove('hidden');
|
|
}
|
|
|
|
function hideUnitDropdown(pickerId = '') {
|
|
const dropdown = document.getElementById('unit-picker-dropdown' + pickerId);
|
|
if (dropdown) dropdown.classList.add('hidden');
|
|
}
|
|
|
|
function handleUnitSearchInput(pickerId, value) {
|
|
const valueInput = document.getElementById('unit-picker-value' + pickerId);
|
|
const clearBtn = document.getElementById('unit-picker-clear' + pickerId);
|
|
|
|
// If user clears the search box, also clear the hidden value
|
|
if (!value.trim()) {
|
|
if (valueInput) valueInput.value = '';
|
|
if (clearBtn) clearBtn.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
// Close dropdown when clicking outside
|
|
document.addEventListener('click', function(event) {
|
|
const pickers = document.querySelectorAll('.unit-picker');
|
|
pickers.forEach(picker => {
|
|
if (!picker.contains(event.target)) {
|
|
const dropdown = picker.querySelector('[id^="unit-picker-dropdown"]');
|
|
if (dropdown) dropdown.classList.add('hidden');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
</script>
|