Add inline assign from dashboard, specific battery picker on device, dynamic install rows

- Dashboard: replace Assign link with device dropdown + arrow button for
  quick inline assignment without leaving the page
- Device detail: replace hardcoded 4-row install form with 1 row + '+ Add
  brand' button that clones rows dynamically
- Device detail: add 'Install Specific Battery' card with dropdown of all
  available batteries (label, brand, size, notes) via new /device/<id>/install-one route
- Tests: 4 new acceptance tests covering dashboard quick-assign and
  install-one, including capacity enforcement on both paths (39 total)
This commit is contained in:
2026-04-12 20:15:29 -05:00
parent 4ad29558b3
commit 81e87d2fe2
4 changed files with 170 additions and 26 deletions
+24 -1
View File
@@ -167,7 +167,18 @@
<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>
<select id="qas-{{ b.id }}"
style="padding:0.2rem 0.3rem;font-size:0.8rem;border:1px solid #cbd5e1;border-radius:4px;max-width:110px;vertical-align:middle;">
<option value="">— assign —</option>
{% for d in devices %}
<option value="{{ d.id }}"
{% if d.installed_count() >= d.battery_slots %}disabled{% endif %}>
{{ d.name }} ({{ d.installed_count() }}/{{ d.battery_slots }})
</option>
{% endfor %}
</select>
<button type="button" class="btn btn-sm btn-primary"
onclick="quickAssign('{{ url_for('battery_assign', battery_id=b.id) }}', {{ b.id }})"></button>
{% endif %}
{% if b.is_installed() %}
@@ -249,6 +260,18 @@ function applyFilters() {
updateToolbar();
}
function quickAssign(action, batteryId) {
var sel = document.getElementById('qas-' + batteryId);
if (!sel.value) { sel.focus(); return; }
var f = document.createElement('form');
f.method = 'post'; f.action = action;
var inp = document.createElement('input');
inp.type = 'hidden'; inp.name = 'device_id'; inp.value = sel.value;
f.appendChild(inp);
document.body.appendChild(f);
f.submit();
}
function resetFilters() {
['filter-status','filter-brand','filter-size','filter-storage'].forEach(function(id) {
document.getElementById(id).value = '';