Add required battery_size to devices, filter install panels by size

This commit is contained in:
2026-04-19 14:48:55 -05:00
parent aabe273172
commit 3e75bb3ab4
8 changed files with 238 additions and 77 deletions
+21
View File
@@ -18,6 +18,27 @@
value="{{ form_slots|default(1) }}" min="1" required>
</div>
<div class="form-group">
<label for="battery-size-select">Battery Size <span class="text-danger">*</span></label>
{% set _preset_sizes = ['AA','AAA','C','D','9V','CR2032','CR2025','CR2016','18650','14500','16340','26650','LR44/AG13'] %}
{% set _cur_size = form_battery_size|default('') %}
<select id="battery-size-select" onchange="metaSelectChanged(this,'battery_size')">
<option value="">— select —</option>
{% for s in _preset_sizes %}
<option value="{{ s }}" {% if _cur_size == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
{% for s in device_battery_sizes|default([]) %}
{% if s not in _preset_sizes %}
<option value="{{ s }}" {% if _cur_size == s %}selected{% endif %}>{{ s }}</option>
{% endif %}
{% endfor %}
<option value="__new__" {% if _cur_size and _cur_size not in _preset_sizes %}selected{% endif %}>Other…</option>
</select>
<input type="text" id="battery_size" name="battery_size" value="{{ _cur_size }}"
placeholder="e.g. CR123A"
style="display:{% if _cur_size and _cur_size not in _preset_sizes %}''{% else %}none{% endif %};margin-top:0.4rem;">
</div>
<div class="form-group">
<label>Type</label>
{% set _preset_types = ['Remote Control','Game Controller','Flashlight','Lock','Sensor','Toy','Clock','Smoke Detector'] %}
+39 -2
View File
@@ -39,6 +39,12 @@
<td style="border:none;">{{ device.device_type }}</td>
</tr>
{% endif %}
{% if device.battery_size %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Battery Size</td>
<td style="border:none;">{{ device.battery_size }}</td>
</tr>
{% endif %}
{% if device.has_mixed_brands() %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Warning</td>
@@ -79,7 +85,7 @@
<div class="card">
<h2>Install Batteries</h2>
{% set free_slots = device.battery_slots - device.installed_count() %}
<p class="text-muted" style="margin-bottom:0.75rem;">{{ free_slots }} slot(s) free</p>
<p class="text-muted" style="margin-bottom:0.75rem;">{{ free_slots }} slot(s) free{% if device.battery_size %} &mdash; showing {{ device.battery_size }} batteries only{% endif %}</p>
<form method="post" action="{{ url_for('device_install', device_id=device.id) }}">
<div id="install-grid" style="display:grid;grid-template-columns:1fr auto;gap:0.4rem;max-width:400px;align-items:start;margin-bottom:0.5rem;">
<span style="font-weight:600;font-size:0.85rem;color:#64748b;">Brand</span>
@@ -115,6 +121,14 @@ function editTypeSelectChanged(sel) {
input.style.display = 'none'; input.value = sel.value;
}
}
function editSizeSelectChanged(sel) {
var input = document.getElementById('edit-battery-size');
if (sel.value === '__new__') {
input.style.display = ''; input.value = ''; input.focus();
} else {
input.style.display = 'none'; input.value = sel.value;
}
}
function editLocationSelectChanged(sel) {
var input = document.getElementById('edit-location');
if (sel.value === '__new__') {
@@ -186,7 +200,7 @@ function addInstallRow() {
</div>
<div class="card">
<h2>Install Specific Battery</h2>
<h2>Install Specific Battery{% if device.battery_size %} <small class="text-muted" style="font-weight:normal;font-size:0.8rem;">({{ device.battery_size }} only)</small>{% endif %}</h2>
{% if available_batteries %}
<form method="post" action="{{ url_for('device_install_one', device_id=device.id) }}">
<div class="form-group">
@@ -238,6 +252,29 @@ function addInstallRow() {
placeholder="Enter device type"
style="display:{% if device.device_type and device.device_type not in _preset_types %}''{% else %}none{% endif %};margin-top:0.4rem;">
</div>
<div class="form-group">
<label>Battery Size</label>
{% set _preset_sizes = ['AA','AAA','C','D','9V','CR2032','CR2025','CR2016','18650','14500','16340','26650','LR44/AG13'] %}
<select id="edit-battery-size-select" onchange="editSizeSelectChanged(this)">
<option value="">— none —</option>
{% for s in _preset_sizes %}
<option value="{{ s }}" {% if device.battery_size == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
{% for s in device_battery_sizes|default([]) %}
{% if s not in _preset_sizes %}
<option value="{{ s }}" {% if device.battery_size == s %}selected{% endif %}>{{ s }}</option>
{% endif %}
{% endfor %}
{% if device.battery_size and device.battery_size not in _preset_sizes and device.battery_size not in device_battery_sizes|default([]) %}
<option value="{{ device.battery_size }}" selected>{{ device.battery_size }}</option>
{% endif %}
<option value="__new__">Other…</option>
</select>
<input type="text" id="edit-battery-size" name="battery_size"
value="{{ device.battery_size or '' }}"
placeholder="e.g. CR123A"
style="display:{% if device.battery_size and device.battery_size not in _preset_sizes %}''{% else %}none{% endif %};margin-top:0.4rem;">
</div>
<div class="form-group">
<label>Location</label>
<select id="edit-location-select" onchange="editLocationSelectChanged(this)">
+33 -18
View File
@@ -20,6 +20,13 @@
<option value="{{ loc }}">{{ loc }}</option>
{% endfor %}
</select>
<select id="filter-battery-size" onchange="applyDeviceFilters()"
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;">
<option value="">Any Size</option>
{% for sz in device_battery_sizes|default([]) %}
<option value="{{ sz }}">{{ sz }}</option>
{% endfor %}
</select>
<select id="filter-fill" onchange="applyDeviceFilters()"
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;">
<option value="">Any Fill</option>
@@ -40,6 +47,7 @@
<tr>
<th>Name</th>
<th>Type</th>
<th>Size</th>
<th>Location</th>
<th>Slots</th>
<th>Installed</th>
@@ -54,11 +62,13 @@
{% elif installed >= d.battery_slots %}{% set fill_state = 'full' %}
{% else %}{% set fill_state = 'partial' %}{% endif %}
<tr data-type="{{ d.device_type or '' }}"
data-battery-size="{{ d.battery_size or '' }}"
data-location="{{ d.location or '' }}"
data-fill="{{ fill_state }}"
data-name="{{ d.name|lower }}">
<td data-label="Device"><a href="{{ url_for('device_detail', device_id=d.id) }}"><strong>{{ d.name }}</strong></a></td>
<td data-label="Type">{{ d.device_type or '—' }}</td>
<td data-label="Size">{{ d.battery_size or '—' }}</td>
<td data-label="Location">{{ d.location or '—' }}</td>
<td data-label="Slots">{{ d.battery_slots }}</td>
<td data-label="Installed">
@@ -95,7 +105,7 @@
</td>
</tr>
{% else %}
<tr><td colspan="7" class="text-muted" style="text-align:center;padding:1rem;">No devices yet. <a href="{{ url_for('device_add') }}">Add one.</a></td></tr>
<tr><td colspan="8" class="text-muted" style="text-align:center;padding:1rem;">No devices yet. <a href="{{ url_for('device_add') }}">Add one.</a></td></tr>
{% endfor %}
</tbody>
</table>
@@ -106,27 +116,31 @@
<script>
function applyDeviceFilters() {
var typeVal = document.getElementById('filter-type').value;
var locationVal = document.getElementById('filter-location').value;
var fillVal = document.getElementById('filter-fill').value;
var textVal = document.getElementById('filter-device-text').value.toLowerCase();
var rows = document.querySelectorAll('tbody tr[data-name]');
var visible = 0;
var typeVal = document.getElementById('filter-type').value;
var batterySizeVal = document.getElementById('filter-battery-size').value;
var locationVal = document.getElementById('filter-location').value;
var fillVal = document.getElementById('filter-fill').value;
var textVal = document.getElementById('filter-device-text').value.toLowerCase();
var rows = document.querySelectorAll('tbody tr[data-name]');
var visible = 0;
rows.forEach(function(row) {
var rowType = row.dataset.type || '';
var rowLocation = row.dataset.location || '';
var rowFill = row.dataset.fill || '';
var rowName = row.dataset.name || '';
var show = (!typeVal || rowType === typeVal) &&
(!locationVal || rowLocation === locationVal) &&
(!fillVal || rowFill === fillVal) &&
(!textVal || rowName.includes(textVal) ||
rowType.toLowerCase().includes(textVal) ||
rowLocation.toLowerCase().includes(textVal));
var rowType = row.dataset.type || '';
var rowBatterySize = row.dataset.batterySize || '';
var rowLocation = row.dataset.location || '';
var rowFill = row.dataset.fill || '';
var rowName = row.dataset.name || '';
var show = (!typeVal || rowType === typeVal) &&
(!batterySizeVal || rowBatterySize === batterySizeVal) &&
(!locationVal || rowLocation === locationVal) &&
(!fillVal || rowFill === fillVal) &&
(!textVal || rowName.includes(textVal) ||
rowType.toLowerCase().includes(textVal) ||
rowBatterySize.toLowerCase().includes(textVal) ||
rowLocation.toLowerCase().includes(textVal));
row.style.display = show ? '' : 'none';
if (show) visible++;
});
var active = typeVal || locationVal || fillVal || textVal;
var active = typeVal || batterySizeVal || locationVal || fillVal || textVal;
document.getElementById('device-filter-reset').style.display = active ? '' : 'none';
document.getElementById('device-filter-count').textContent =
active ? (visible + ' of ' + rows.length + ' shown') : '';
@@ -134,6 +148,7 @@ function applyDeviceFilters() {
function resetDeviceFilters() {
document.getElementById('filter-type').value = '';
document.getElementById('filter-battery-size').value = '';
document.getElementById('filter-location').value = '';
document.getElementById('filter-fill').value = '';
document.getElementById('filter-device-text').value = '';