Add sub-component support via self-referential device hierarchy
This commit is contained in:
@@ -264,6 +264,10 @@
|
||||
</td>
|
||||
<td data-label="Assigned To">
|
||||
{% if b.device %}
|
||||
{% if b.device.parent %}
|
||||
<a href="{{ url_for('device_detail', device_id=b.device.parent.id) }}">{{ b.device.parent.name }}</a>
|
||||
<span class="text-muted"> / </span>
|
||||
{% endif %}
|
||||
<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>
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
{% block title %}Add Device — Battery Tracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Add Device</h1>
|
||||
<h1>{% if prefill_parent %}Add Sub-component to {{ prefill_parent.name }}{% else %}Add Device{% endif %}</h1>
|
||||
|
||||
<div class="card">
|
||||
<form method="post" action="{{ url_for('device_add') }}">
|
||||
{% if prefill_parent %}
|
||||
<input type="hidden" name="parent_id" value="{{ prefill_parent.id }}">
|
||||
<p class="text-muted" style="margin-bottom:0.75rem;">
|
||||
Adding as a sub-component of <a href="{{ url_for('device_detail', device_id=prefill_parent.id) }}">{{ prefill_parent.name }}</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label for="name">Device Name <span class="text-danger">*</span></label>
|
||||
<input type="text" id="name" name="name" value="{{ form_name|default('') }}"
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
{% block title %}{{ device.name }} — Battery Tracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if device.parent %}
|
||||
<div style="margin-bottom:0.5rem;font-size:0.9rem;">
|
||||
<a href="{{ url_for('device_detail', device_id=device.parent.id) }}">← {{ device.parent.name }}</a>
|
||||
<span class="text-muted"> / {{ device.name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1>{{ device.name }}</h1>
|
||||
|
||||
<div class="card" style="position:relative;">
|
||||
@@ -63,6 +69,12 @@
|
||||
<td style="border:none;">{{ device.notes }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if device.parent %}
|
||||
<tr>
|
||||
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Part of</td>
|
||||
<td style="border:none;"><a href="{{ url_for('device_detail', device_id=device.parent.id) }}">{{ device.parent.name }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if ha_enabled and device.ha_entity_id %}
|
||||
<tr>
|
||||
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">HA Live %</td>
|
||||
@@ -82,6 +94,27 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if device.children or not device.is_subcomponent() %}
|
||||
<div class="card">
|
||||
{% if device.children %}
|
||||
<h2>Sub-components</h2>
|
||||
<div style="display:flex;gap:0.75rem;flex-wrap:wrap;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 · {{ child.battery_size }}{% if child.ha_entity_id %} · HA{% endif %}</small>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<h2>Sub-components</h2>
|
||||
<p class="text-muted" style="margin-bottom:0.75rem;">No sub-components. Batteries are tracked at the device level.</p>
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-secondary" href="{{ url_for('device_add') }}?parent_id={{ device.id }}">+ Add Sub-component</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<h2>Install Batteries</h2>
|
||||
{% set free_slots = device.battery_slots - device.installed_count() %}
|
||||
@@ -401,6 +434,19 @@ function addInstallRow() {
|
||||
<label for="edit-notes">Notes</label>
|
||||
<textarea id="edit-notes" name="notes">{{ device.notes or '' }}</textarea>
|
||||
</div>
|
||||
{% if not device.has_children() %}
|
||||
<div class="form-group">
|
||||
<label for="edit-parent">Part of Device (optional)</label>
|
||||
<select id="edit-parent" name="parent_id">
|
||||
<option value="">— none (top-level device) —</option>
|
||||
{% for d in device_list_all|default([]) %}
|
||||
{% if d.id != device.id and not d.is_subcomponent() %}
|
||||
<option value="{{ d.id }}" {% if device.parent_id == d.id %}selected{% endif %}>{{ d.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if ha_enabled %}
|
||||
<div class="form-group">
|
||||
<label for="edit-ha-entity">Home Assistant Entity ID</label>
|
||||
@@ -426,6 +472,9 @@ function addInstallRow() {
|
||||
<h2>Delete Device</h2>
|
||||
<p style="margin-bottom:1rem;" class="text-muted">
|
||||
Deleting this device will unassign all installed batteries and mark them available.
|
||||
{% if device.has_children() %}
|
||||
Sub-components ({{ device.children|map(attribute='name')|join(', ') }}) will become independent devices.
|
||||
{% endif %}
|
||||
</p>
|
||||
<form method="post" action="{{ url_for('device_delete', device_id=device.id) }}">
|
||||
<button class="btn btn-danger" type="submit">Delete {{ device.name }}</button>
|
||||
|
||||
@@ -66,7 +66,14 @@
|
||||
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>
|
||||
{% if d.parent %}
|
||||
<br><small class="text-muted">↳ <a href="{{ url_for('device_detail', device_id=d.parent.id) }}">{{ d.parent.name }}</a></small>
|
||||
{% elif d.has_children() %}
|
||||
<br><small class="text-muted">{{ d.children|length }} sub-component{{ 's' if d.children|length != 1 }}</small>
|
||||
{% endif %}
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user