Add location and fill-status filters to device list
This commit is contained in:
@@ -633,7 +633,9 @@ def create_app(config_object="config"):
|
|||||||
def device_list():
|
def device_list():
|
||||||
devices = db.query(Device).order_by(Device.name).all()
|
devices = db.query(Device).order_by(Device.name).all()
|
||||||
device_types = sorted({d.device_type for d in devices if d.device_type})
|
device_types = sorted({d.device_type for d in devices if d.device_type})
|
||||||
return render_template("device_list.html", devices=devices, device_types=device_types)
|
device_locations = sorted({d.location for d in devices if d.location})
|
||||||
|
return render_template("device_list.html", devices=devices,
|
||||||
|
device_types=device_types, device_locations=device_locations)
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Devices — add
|
# Devices — add
|
||||||
|
|||||||
+43
-10
@@ -13,6 +13,20 @@
|
|||||||
<option value="{{ t }}">{{ t }}</option>
|
<option value="{{ t }}">{{ t }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<select id="filter-location" onchange="applyDeviceFilters()"
|
||||||
|
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;">
|
||||||
|
<option value="">All Locations</option>
|
||||||
|
{% for loc in device_locations|default([]) %}
|
||||||
|
<option value="{{ loc }}">{{ loc }}</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>
|
||||||
|
<option value="empty">Empty</option>
|
||||||
|
<option value="partial">Partial</option>
|
||||||
|
<option value="full">Full</option>
|
||||||
|
</select>
|
||||||
<input type="text" id="filter-device-text" oninput="applyDeviceFilters()" placeholder="Search…"
|
<input type="text" id="filter-device-text" oninput="applyDeviceFilters()" placeholder="Search…"
|
||||||
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;width:140px;">
|
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;width:140px;">
|
||||||
<button type="button" onclick="resetDeviceFilters()" class="btn btn-sm btn-secondary"
|
<button type="button" onclick="resetDeviceFilters()" class="btn btn-sm btn-secondary"
|
||||||
@@ -26,6 +40,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
|
<th>Location</th>
|
||||||
<th>Slots</th>
|
<th>Slots</th>
|
||||||
<th>Installed</th>
|
<th>Installed</th>
|
||||||
<th>Brands</th>
|
<th>Brands</th>
|
||||||
@@ -34,13 +49,21 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for d in devices %}
|
{% for d in devices %}
|
||||||
<tr data-type="{{ d.device_type or '' }}" data-name="{{ d.name|lower }}">
|
{% set installed = d.installed_count() %}
|
||||||
|
{% if installed == 0 %}{% set fill_state = 'empty' %}
|
||||||
|
{% elif installed >= d.battery_slots %}{% set fill_state = 'full' %}
|
||||||
|
{% else %}{% set fill_state = 'partial' %}{% endif %}
|
||||||
|
<tr data-type="{{ d.device_type 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="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="Type">{{ d.device_type or '—' }}</td>
|
||||||
|
<td data-label="Location">{{ d.location or '—' }}</td>
|
||||||
<td data-label="Slots">{{ d.battery_slots }}</td>
|
<td data-label="Slots">{{ d.battery_slots }}</td>
|
||||||
<td data-label="Installed">
|
<td data-label="Installed">
|
||||||
{{ d.installed_count() }} / {{ d.battery_slots }}
|
{{ installed }} / {{ d.battery_slots }}
|
||||||
{% if d.installed_count() >= d.battery_slots %}
|
{% if installed >= d.battery_slots %}
|
||||||
<span class="badge badge-retired">Full</span>
|
<span class="badge badge-retired">Full</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@@ -57,7 +80,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td data-label="Actions" style="white-space:nowrap;">
|
<td data-label="Actions" style="white-space:nowrap;">
|
||||||
<a class="btn btn-sm btn-secondary" href="{{ url_for('device_detail', device_id=d.id) }}">View</a>
|
<a class="btn btn-sm btn-secondary" href="{{ url_for('device_detail', device_id=d.id) }}">View</a>
|
||||||
{% if d.installed_count() > 0 %}
|
{% if installed > 0 %}
|
||||||
<form class="inline" method="post" action="{{ url_for('device_unassign_all', device_id=d.id) }}"
|
<form class="inline" method="post" action="{{ url_for('device_unassign_all', device_id=d.id) }}"
|
||||||
data-confirm="Unassign all batteries from {{ d.name }}?"
|
data-confirm="Unassign all batteries from {{ d.name }}?"
|
||||||
data-confirm-ok="Unassign" data-confirm-class="btn-warning">
|
data-confirm-ok="Unassign" data-confirm-class="btn-warning">
|
||||||
@@ -72,7 +95,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="6" 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="7" class="text-muted" style="text-align:center;padding:1rem;">No devices yet. <a href="{{ url_for('device_add') }}">Add one.</a></td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -83,19 +106,27 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
function applyDeviceFilters() {
|
function applyDeviceFilters() {
|
||||||
var typeVal = document.getElementById('filter-type').value.toLowerCase();
|
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 textVal = document.getElementById('filter-device-text').value.toLowerCase();
|
||||||
var rows = document.querySelectorAll('tbody tr[data-name]');
|
var rows = document.querySelectorAll('tbody tr[data-name]');
|
||||||
var visible = 0;
|
var visible = 0;
|
||||||
rows.forEach(function(row) {
|
rows.forEach(function(row) {
|
||||||
var rowType = (row.dataset.type || '').toLowerCase();
|
var rowType = row.dataset.type || '';
|
||||||
var rowName = (row.dataset.name || '').toLowerCase();
|
var rowLocation = row.dataset.location || '';
|
||||||
|
var rowFill = row.dataset.fill || '';
|
||||||
|
var rowName = row.dataset.name || '';
|
||||||
var show = (!typeVal || rowType === typeVal) &&
|
var show = (!typeVal || rowType === typeVal) &&
|
||||||
(!textVal || rowName.includes(textVal) || rowType.includes(textVal));
|
(!locationVal || rowLocation === locationVal) &&
|
||||||
|
(!fillVal || rowFill === fillVal) &&
|
||||||
|
(!textVal || rowName.includes(textVal) ||
|
||||||
|
rowType.toLowerCase().includes(textVal) ||
|
||||||
|
rowLocation.toLowerCase().includes(textVal));
|
||||||
row.style.display = show ? '' : 'none';
|
row.style.display = show ? '' : 'none';
|
||||||
if (show) visible++;
|
if (show) visible++;
|
||||||
});
|
});
|
||||||
var active = typeVal || textVal;
|
var active = typeVal || locationVal || fillVal || textVal;
|
||||||
document.getElementById('device-filter-reset').style.display = active ? '' : 'none';
|
document.getElementById('device-filter-reset').style.display = active ? '' : 'none';
|
||||||
document.getElementById('device-filter-count').textContent =
|
document.getElementById('device-filter-count').textContent =
|
||||||
active ? (visible + ' of ' + rows.length + ' shown') : '';
|
active ? (visible + ' of ' + rows.length + ' shown') : '';
|
||||||
@@ -103,6 +134,8 @@ function applyDeviceFilters() {
|
|||||||
|
|
||||||
function resetDeviceFilters() {
|
function resetDeviceFilters() {
|
||||||
document.getElementById('filter-type').value = '';
|
document.getElementById('filter-type').value = '';
|
||||||
|
document.getElementById('filter-location').value = '';
|
||||||
|
document.getElementById('filter-fill').value = '';
|
||||||
document.getElementById('filter-device-text').value = '';
|
document.getElementById('filter-device-text').value = '';
|
||||||
applyDeviceFilters();
|
applyDeviceFilters();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user