Files
battery-tracker-app/templates/dashboard.html
T
iterminate 6384f6b589 Add bulk set field (Storage Location, Brand) to dashboard toolbar
Select batteries, choose field and value from toolbar, hit Apply.
Storage Location dropdown populated from existing locations.
2026-04-12 15:48:46 -05:00

184 lines
8.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Dashboard — Battery Tracker{% endblock %}
{% block content %}
<h1>Battery Dashboard</h1>
{% set total = batteries|length %}
{% set available = batteries|selectattr('status','eq','available')|list|length %}
{% set installed = batteries|selectattr('status','eq','installed')|list|length %}
{% set retired = batteries|selectattr('status','eq','retired')|list|length %}
<div style="display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1.25rem;">
<div class="card" style="flex:1;min-width:120px;text-align:center;">
<div style="font-size:1.8rem;font-weight:700;">{{ total }}</div>
<div class="text-muted">Total</div>
</div>
<div class="card" style="flex:1;min-width:120px;text-align:center;">
<div style="font-size:1.8rem;font-weight:700;color:#166534;">{{ available }}</div>
<div class="text-muted">Available</div>
</div>
<div class="card" style="flex:1;min-width:120px;text-align:center;">
<div style="font-size:1.8rem;font-weight:700;color:#1e40af;">{{ installed }}</div>
<div class="text-muted">Installed</div>
</div>
<div class="card" style="flex:1;min-width:120px;text-align:center;">
<div style="font-size:1.8rem;font-weight:700;color:#64748b;">{{ retired }}</div>
<div class="text-muted">Retired</div>
</div>
</div>
<div class="card">
<form method="post" action="{{ url_for('battery_bulk_action') }}" id="bulk-form">
<div id="bulk-toolbar" style="display:none;margin-bottom:0.75rem;padding:0.6rem 0.75rem;background:#f1f5f9;border-radius:6px;display:none;align-items:center;gap:0.5rem;flex-wrap:wrap;">
<span id="selected-count" style="font-size:0.85rem;color:#64748b;margin-right:0.25rem;"></span>
<button class="btn btn-sm btn-warning" name="action" value="unassign" type="submit">Unassign</button>
<button class="btn btn-sm btn-secondary" name="action" value="retire" type="submit">Retire</button>
<button class="btn btn-sm btn-danger" name="action" value="delete" type="submit"
onclick="return confirm('Permanently delete selected batteries?')">Delete</button>
<span style="display:flex;gap:0.35rem;align-items:center;flex-wrap:wrap;">
<input type="hidden" name="field_name" id="bulk-field-name" value="storage_location">
<select id="bulk-field-select" onchange="updateBulkField(this)"
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;">
<option value="storage_location">Storage Location</option>
<option value="brand">Brand</option>
</select>
<!-- Storage Location value -->
<span id="bulk-val-storage_location" style="display:flex;gap:0.25rem;align-items:center;">
<select id="bulk-storage-select" onchange="bulkStorageChanged(this)"
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;">
<option value="">— select —</option>
{% for loc in storage_locations|default([]) %}
<option value="{{ loc }}">{{ loc }}</option>
{% endfor %}
<option value="__new__"> New location…</option>
</select>
<input type="text" id="bulk-storage-text"
style="display:none;padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;width:140px;"
placeholder="Type location">
<input type="hidden" name="field_value" id="bulk-field-value-storage" value="">
</span>
<!-- Brand value -->
<span id="bulk-val-brand" style="display:none;">
<input type="text" id="bulk-brand-text" oninput="document.getElementById('bulk-field-value-brand').value=this.value"
style="padding:0.25rem 0.5rem;font-size:0.85rem;border:1px solid #cbd5e1;border-radius:4px;width:160px;"
placeholder="New brand name">
<input type="hidden" name="field_value" id="bulk-field-value-brand" value="">
</span>
<button class="btn btn-sm btn-primary" name="action" value="set_field" type="submit">Apply</button>
</span>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:1.5rem;"><input type="checkbox" id="select-all" title="Select all"></th>
<th>Label</th>
<th>Brand</th>
<th>Size</th>
<th>Status</th>
<th>Assigned To</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for b in batteries %}
<tr>
<td><input type="checkbox" name="battery_ids" value="{{ b.id }}" class="row-cb"></td>
<td><a href="{{ url_for('battery_detail', battery_id=b.id) }}"><strong>{{ b.label }}</strong></a></td>
<td>{{ b.brand }}</td>
<td>{{ b.size or '—' }}</td>
<td>
<span class="badge badge-{{ b.status }}">{{ b.status|capitalize }}</span>
</td>
<td>
{% if b.device %}
<a href="{{ url_for('device_detail', device_id=b.device.id) }}">{{ b.device.name }}</a>
{% if b.device.has_mixed_brands() %}
<span class="badge badge-warning" title="Mixed brands in this device">⚠ mixed</span>
{% endif %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
<td style="white-space:nowrap;">
<a class="btn btn-sm btn-secondary" href="{{ url_for('battery_detail', battery_id=b.id) }}">View</a>
{% if b.is_available() %}
<a class="btn btn-sm btn-primary" href="{{ url_for('battery_assign', battery_id=b.id) }}">Assign</a>
{% endif %}
{% if b.is_installed() %}
<button class="btn btn-sm btn-warning" type="submit"
formaction="{{ url_for('battery_unassign', battery_id=b.id) }}">Unassign</button>
{% endif %}
{% if not b.is_retired() %}
<button class="btn btn-sm btn-secondary" type="submit"
formaction="{{ url_for('battery_retire', battery_id=b.id) }}">Retire</button>
{% endif %}
</td>
</tr>
{% else %}
<tr><td colspan="7" class="text-muted" style="text-align:center;padding:1rem;">No batteries found. <a href="{{ url_for('battery_add') }}">Add some.</a></td></tr>
{% endfor %}
</tbody>
</table>
</div>
</form>
</div>
<script>
(function () {
var cbs = document.querySelectorAll('.row-cb');
var selectAll = document.getElementById('select-all');
var toolbar = document.getElementById('bulk-toolbar');
var countEl = document.getElementById('selected-count');
function updateToolbar() {
var checked = document.querySelectorAll('.row-cb:checked');
var n = checked.length;
toolbar.style.display = n > 0 ? 'flex' : 'none';
countEl.textContent = n + ' selected';
selectAll.indeterminate = n > 0 && n < cbs.length;
selectAll.checked = cbs.length > 0 && n === cbs.length;
}
cbs.forEach(function (cb) { cb.addEventListener('change', updateToolbar); });
selectAll.addEventListener('change', function () {
cbs.forEach(function (cb) { cb.checked = selectAll.checked; });
updateToolbar();
});
}());
function updateBulkField(sel) {
var field = sel.value;
document.getElementById('bulk-field-name').value = field;
document.getElementById('bulk-val-storage_location').style.display = field === 'storage_location' ? 'flex' : 'none';
document.getElementById('bulk-val-brand').style.display = field === 'brand' ? 'flex' : 'none';
document.getElementById('bulk-field-value-storage').disabled = (field !== 'storage_location');
document.getElementById('bulk-field-value-brand').disabled = (field !== 'brand');
}
// initialise disabled state on page load
document.getElementById('bulk-field-value-brand').disabled = true;
function bulkStorageChanged(sel) {
var text = document.getElementById('bulk-storage-text');
var hidden = document.getElementById('bulk-field-value-storage');
if (sel.value === '__new__') {
text.style.display = '';
text.value = '';
text.oninput = function() { hidden.value = text.value; };
text.focus();
hidden.value = '';
} else {
text.style.display = 'none';
hidden.value = sel.value;
}
}
</script>
{% endblock %}