Redirect to device detail after create; sub-component install UI on parent detail

This commit is contained in:
2026-06-09 21:18:22 -05:00
parent 23eeeafff7
commit 27ed4c9420
2 changed files with 82 additions and 8 deletions
+10 -2
View File
@@ -759,7 +759,7 @@ def create_app(config_object="config"):
flash(f"Device '{name}' added.", "success")
if parent_device:
return redirect(url_for("device_detail", device_id=parent_device.id))
return redirect(url_for("device_list"))
return redirect(url_for("device_detail", device_id=device.id))
return render_template("device_add.html", device_types=device_types,
device_locations=device_locations,
@@ -809,11 +809,18 @@ def create_app(config_object="config"):
if changed:
db.commit()
flat_installed = []
children_avail = {}
if device.has_children():
for child in device.children:
for b in child.batteries:
if b.status == "installed":
flat_installed.append((b, child.name, child.id))
avail_q = db.query(Battery).filter_by(status="available")
if child.battery_size:
avail_q = avail_q.filter(
(Battery.size == child.battery_size) | (Battery.size == None)
)
children_avail[child.id] = avail_q.order_by(Battery.label).all()
return render_template("device_detail.html", device=device, brands=brands,
available_batteries=available_batteries,
device_types=device_types,
@@ -823,7 +830,8 @@ def create_app(config_object="config"):
ha_enabled=ha_client.enabled,
ha_live_pct=ha_live_pct,
logbook_entries=device.logbook_entries,
flat_installed=flat_installed)
flat_installed=flat_installed,
children_avail=children_avail)
# ------------------------------------------------------------------ #
# Devices — edit
+72 -6
View File
@@ -112,13 +112,79 @@
<div class="card">
{% if device.children %}
<h2>Sub-components</h2>
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;margin-bottom:0.75rem;">
<div style="display:flex;flex-direction:column;gap:0.5rem;margin-bottom:0.75rem;">
{% for child in device.children %}
<a href="{{ url_for('device_detail', device_id=child.id) }}"
style="display:block;padding:0.6rem 0.9rem;border:1px solid var(--border);border-radius:6px;text-decoration:none;min-width:140px;background:var(--bg-card);">
<strong>{{ child.name }}</strong><br>
<small class="text-muted">{{ child.installed_count() }}/{{ child.battery_slots }} slots &middot; {{ child.battery_size }}{% if child.ha_entity_id %} &middot; HA{% endif %}</small>
</a>
{% set child_free = child.battery_slots - child.installed_count() %}
{% set child_batteries = children_avail.get(child.id, []) %}
<details style="border:1px solid var(--border);border-radius:6px;background:var(--bg-card);">
<summary style="padding:0.6rem 0.9rem;cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;">
<span>
<strong>{{ child.name }}</strong>
<small class="text-muted" style="margin-left:0.5rem;">{{ child.installed_count() }}/{{ child.battery_slots }} slots &middot; {{ child.battery_size }}{% if child.ha_entity_id %} &middot; HA{% endif %}</small>
</span>
<span style="display:flex;gap:0.5rem;align-items:center;">
{% if child_free > 0 and child_batteries %}<small class="text-muted">{{ child_batteries|length }} available</small>{% endif %}
<a href="{{ url_for('device_detail', device_id=child.id) }}" onclick="event.stopPropagation();" style="font-size:0.8rem;">View</a>
</span>
</summary>
<div style="padding:0.6rem 0.9rem;border-top:1px solid var(--border);">
{% if child_free <= 0 %}
<p class="text-muted" style="margin:0;">No free slots.</p>
{% elif not child_batteries %}
<p class="text-muted" style="margin:0;">No compatible batteries available.</p>
{% else %}
<form method="post" action="{{ url_for('device_install_batch', device_id=child.id) }}">
<div style="margin-bottom:0.4rem;">
<label style="font-size:0.85rem;cursor:pointer;">
<input type="checkbox" id="select-all-child-{{ child.id }}" onchange="toggleAllChild{{ child.id }}(this)"> Select all
</label>
</div>
<div style="max-height:180px;overflow-y:auto;border:1px solid #cbd5e1;border-radius:4px;padding:0.4rem 0.6rem;">
{% for b in child_batteries %}
<div>
<label style="font-size:0.9rem;cursor:pointer;">
<input type="checkbox" class="child-bat-{{ child.id }}" name="battery_ids" value="{{ b.id }}">
{{ b.label }} — {{ b.brand }}{% if b.battery_percentage is not none %} ({{ b.battery_percentage }}%){% endif %}
</label>
</div>
{% endfor %}
</div>
<button class="btn btn-sm btn-primary" type="submit" style="margin-top:0.5rem;">Install Selected</button>
</form>
<script>
(function() {
var FREE_{{ child.id }} = {{ child_free }};
var queue_{{ child.id }} = [];
document.querySelectorAll('.child-bat-{{ child.id }}').forEach(function(cb) {
cb.addEventListener('change', function() {
if (cb.checked) {
queue_{{ child.id }}.push(cb);
if (queue_{{ child.id }}.length > FREE_{{ child.id }}) {
queue_{{ child.id }}.shift().checked = false;
}
} else {
queue_{{ child.id }} = queue_{{ child.id }}.filter(function(c) { return c !== cb; });
}
document.getElementById('select-all-child-{{ child.id }}').checked = false;
});
});
window['toggleAllChild{{ child.id }}'] = function(masterCb) {
var all = Array.from(document.querySelectorAll('.child-bat-{{ child.id }}'));
if (masterCb.checked) {
queue_{{ child.id }} = [];
all.forEach(function(c) { c.checked = false; });
all.slice(0, FREE_{{ child.id }}).forEach(function(c) { c.checked = true; queue_{{ child.id }}.push(c); });
if (all.length > FREE_{{ child.id }}) masterCb.checked = false;
} else {
all.forEach(function(c) { c.checked = false; });
queue_{{ child.id }} = [];
}
};
})();
</script>
{% endif %}
</div>
</details>
{% endfor %}
</div>
{% else %}