Files
battery-tracker-app/templates/device_detail.html
T

691 lines
31 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 %}{{ 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) }}">&larr; {{ device.parent.name }}</a>
<span class="text-muted"> / {{ device.name }}</span>
</div>
{% endif %}
<h1>{{ device.name }}</h1>
<div class="card" style="position:relative;">
{% if ha_enabled and device.ha_entity_id and ha_live_pct is not none %}
{% set _pct = ha_live_pct %}
{% set _fill = ((_pct / 100) * 48) | int %}
{% if _pct < 20 %}{% set _color = '#ef4444' %}
{% elif _pct < 60 %}{% set _color = '#f59e0b' %}
{% else %}{% set _color = '#22c55e' %}{% endif %}
<div title="HA battery: {{ _pct }}%"
style="position:absolute;top:1rem;right:1rem;display:flex;flex-direction:column;align-items:center;gap:0.2rem;opacity:0.9;">
<svg width="54" height="26" viewBox="0 0 60 28" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="4" width="52" height="20" rx="3" ry="3"
fill="none" stroke="var(--text-muted)" stroke-width="2"/>
<rect x="54" y="10" width="5" height="8" rx="1.5"
fill="var(--text-muted)"/>
{% if _fill > 0 %}
<rect x="3" y="6" width="{{ _fill }}" height="16" rx="2"
fill="{{ _color }}"/>
{% endif %}
<text x="27" y="19" text-anchor="middle"
font-size="10" font-weight="600" fill="#fff"
style="text-shadow:0 0 3px rgba(0,0,0,0.6);">{{ _pct }}%</text>
</svg>
</div>
{% endif %}
<table style="width:auto;border:none;">
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Slots</td>
<td style="border:none;">{% if device.has_children() %}{% set ns = namespace(inst=0, slots=0) %}{% for c in device.children %}{% set ns.inst = ns.inst + c.installed_count() %}{% set ns.slots = ns.slots + c.battery_slots %}{% endfor %}{{ ns.inst }} / {{ ns.slots }} (across sub-components){% else %}{{ device.installed_count() }} / {{ device.battery_slots }} used{% endif %}</td>
</tr>
{% if device.device_type %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Type</td>
<td style="border:none;">{{ device.device_type }}</td>
</tr>
{% endif %}
{% if device.battery_size %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Battery Size</td>
<td style="border:none;">{{ device.battery_size }}</td>
</tr>
{% endif %}
{% if device.has_mixed_brands() %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Warning</td>
<td style="border:none;"><span class="badge badge-warning">⚠ Mixed brands installed</span></td>
</tr>
{% endif %}
{% if device.location %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Location</td>
<td style="border:none;">{{ device.location }}</td>
</tr>
{% endif %}
{% if device.is_subcomponent() and device.parent %}
{% if device.parent.device_type %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Type</td>
<td style="border:none;">{{ device.parent.device_type }} <span class="text-muted" style="font-size:0.85em;">(inherited)</span></td>
</tr>
{% endif %}
{% if device.parent.location %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Location</td>
<td style="border:none;">{{ device.parent.location }} <span class="text-muted" style="font-size:0.85em;">(inherited)</span></td>
</tr>
{% endif %}
{% endif %}
{% if device.notes %}
<tr>
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Notes</td>
<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>
<td style="border:none;">
{% if ha_live_pct is not none %}
{% if ha_live_pct < 20 %}
<span class="badge badge-warning">⚠ {{ ha_live_pct }}%</span>
{% else %}
{{ ha_live_pct }}%
{% endif %}
{% else %}
<span class="text-muted"></span>
{% endif %}
</td>
</tr>
{% endif %}
</table>
</div>
{% if device.children or not device.is_subcomponent() %}
<div class="card">
{% if device.children %}
<h2>Sub-components</h2>
<div style="display:flex;flex-direction:column;gap:0.5rem;margin-bottom:0.75rem;">
{% for child in device.children %}
{% 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 %}
<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 %}
{% if device.has_children() %}
<div class="card" id="installed">
<h2>Installed Batteries</h2>
{% if flat_installed %}
<div class="table-wrap">
<table class="responsive-table">
<thead><tr>
<th>Component</th><th>Label</th><th>Brand</th>
{% if ha_enabled %}<th>Bat %</th>{% endif %}
<th>Last Charged</th>
</tr></thead>
<tbody>
{% for b, comp_name, comp_id in flat_installed %}
<tr>
<td data-label="Component"><a href="{{ url_for('device_detail', device_id=comp_id) }}">{{ comp_name }}</a></td>
<td data-label="Label"><a href="{{ url_for('battery_detail', battery_id=b.id) }}">{{ b.label }}</a></td>
<td data-label="Brand">{{ b.brand }}</td>
{% if ha_enabled %}
<td data-label="Bat %">
{% if b.battery_percentage is not none %}
{% if b.battery_percentage < 20 %}<span class="badge badge-warning">⚠ {{ b.battery_percentage }}%</span>
{% else %}{{ b.battery_percentage }}%{% endif %}
{% else %}—{% endif %}
</td>
{% endif %}
<td data-label="Last Charged" class="text-muted">{{ b.charge_logs[-1].charged_date if b.charge_logs else '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">No batteries installed across sub-components.</p>
{% endif %}
</div>
{% endif %}
{% if not device.has_children() %}
<div class="card">
<h2>Install Batteries</h2>
{% set free_slots = device.battery_slots - device.installed_count() %}
<p class="text-muted" style="margin-bottom:0.75rem;">{{ free_slots }} slot(s) free{% if device.battery_size %} &mdash; showing {{ device.battery_size }} batteries only{% endif %}</p>
<form method="post" action="{{ url_for('device_install', device_id=device.id) }}">
<div id="install-grid" style="display:grid;grid-template-columns:1fr auto;gap:0.4rem;max-width:400px;align-items:start;margin-bottom:0.5rem;">
<span style="font-weight:600;font-size:0.85rem;color:#64748b;">Brand</span>
<span style="font-weight:600;font-size:0.85rem;color:#64748b;">Qty</span>
<div class="install-row-pair" style="display:contents;">
<div>
<select onchange="brandSelectChanged(this)">
<option value="">— select —</option>
{% for b in brands|default([]) %}
<option value="{{ b }}">{{ b }}</option>
{% endfor %}
<option value="__new__"> New brand…</option>
</select>
<input type="text" name="brand[]" value=""
placeholder="Type brand name"
style="display:none;margin-top:0.3rem;padding:0.3rem 0.5rem;">
</div>
<input type="number" name="qty[]" value="0" min="0"
style="padding:0.3rem 0.5rem;width:4rem;text-align:center;">
</div>
</div>
<button type="button" class="btn btn-sm btn-secondary" onclick="addInstallRow()" style="margin-bottom:0.75rem;">+ Add brand</button>
<br>
<button class="btn btn-primary" type="submit">Install</button>
</form>
<script>
function editTypeSelectChanged(sel) {
var input = document.getElementById('edit-device-type');
if (sel.value === '__new__') {
input.style.display = ''; input.value = ''; input.focus();
} else {
input.style.display = 'none'; input.value = sel.value;
}
}
function editSizeSelectChanged(sel) {
var input = document.getElementById('edit-battery-size');
if (sel.value === '__new__') {
input.style.display = ''; input.value = ''; input.focus();
} else {
input.style.display = 'none'; input.value = sel.value;
}
}
function editLocationSelectChanged(sel) {
var input = document.getElementById('edit-location');
if (sel.value === '__new__') {
input.style.display = ''; input.value = ''; input.focus();
} else {
input.style.display = 'none'; input.value = sel.value;
}
}
function brandSelectChanged(sel) {
var input = sel.nextElementSibling;
if (sel.value === '__new__') {
input.style.display = ''; input.value = ''; input.focus();
} else {
input.style.display = 'none';
input.value = (sel.value === '') ? '' : sel.value;
}
}
function addInstallRow() {
var grid = document.getElementById('install-grid');
var tmpl = grid.querySelector('.install-row-pair');
var clone = tmpl.cloneNode(true);
clone.querySelector('select').value = '';
clone.querySelector('input[type=text]').style.display = 'none';
clone.querySelector('input[type=text]').value = '';
clone.querySelector('input[type=number]').value = '0';
grid.appendChild(clone);
}
</script>
</div>
{% endif %}
{% if not device.has_children() %}
<div class="card" id="installed">
<h2>Installed Batteries</h2>
{% set installed = device.batteries | selectattr('status', 'eq', 'installed') | list %}
{% if installed %}
<div class="table-wrap">
<table class="responsive-table">
<thead>
<tr><th>Label</th><th>Brand</th>{% if ha_enabled %}<th>Bat %</th>{% endif %}<th>Last Charged</th><th>Notes</th><th>Actions</th></tr>
</thead>
<tbody>
{% for b in installed %}
<tr>
<td data-label="Label"><a href="{{ url_for('battery_detail', battery_id=b.id) }}">{{ b.label }}</a></td>
<td data-label="Brand">{{ b.brand }}</td>
{% if ha_enabled %}
<td data-label="Bat %">
{% if b.battery_percentage is not none %}
{% if b.battery_percentage < 20 %}
<span class="badge badge-warning" title="Low — consider replacing">⚠ {{ b.battery_percentage }}%</span>
{% else %}{{ b.battery_percentage }}%{% endif %}
{% else %}—{% endif %}
</td>
{% endif %}
<td data-label="Last Charged" class="text-muted">{{ b.charge_logs[-1].charged_date if b.charge_logs else '—' }}</td>
<td data-label="Notes" class="text-muted">{{ b.notes or '—' }}</td>
<td data-label="Actions">
<form class="inline" method="post" action="{{ url_for('battery_unassign', battery_id=b.id) }}">
<input type="hidden" name="next" value="{{ url_for('device_detail', device_id=device.id) }}#installed">
<button class="btn btn-sm btn-warning" type="submit">Unassign</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">No batteries installed.</p>
{% endif %}
{% if installed %}
<form class="inline" method="post"
action="{{ url_for('device_unassign_all', device_id=device.id) }}"
data-confirm="Unassign all batteries from {{ device.name }}?"
data-confirm-ok="Unassign" data-confirm-class="btn-warning"
style="margin-top:0.5rem;">
<input type="hidden" name="next" value="{{ url_for('device_detail', device_id=device.id) }}#installed">
<button class="btn btn-sm btn-warning" type="submit">Unassign All</button>
</form>
{% endif %}
</div>
{% if installed %}
<div class="card">
<h2>Charge All Installed Batteries</h2>
<form method="post" action="{{ url_for('device_charge_all', device_id=device.id) }}"
style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:flex-end;">
<div class="form-group" style="margin:0;flex:1;min-width:140px;">
<label>Date</label>
<input type="date" name="charged_date" required>
</div>
<div class="form-group" style="margin:0;align-self:flex-end;padding-bottom:1rem;">
<label style="display:flex;align-items:center;gap:0.4rem;font-weight:normal;cursor:pointer;">
<input type="checkbox" name="increment_cycles" value="1" checked>
Increment charge cycles
</label>
</div>
<div class="form-group" style="margin:0;flex:2;min-width:160px;">
<label>Notes (optional)</label>
<input type="text" name="notes" placeholder="e.g. overnight charge">
</div>
<div style="padding-bottom:1rem;">
<button class="btn btn-primary" type="submit">Log Charge for All</button>
</div>
</form>
</div>
{% endif %}
<div class="card">
<h2>Install Specific Batteries{% if device.battery_size %} <small class="text-muted" style="font-weight:normal;font-size:0.8rem;">({{ device.battery_size }} only)</small>{% endif %}</h2>
{% if available_batteries %}
<form method="post" action="{{ url_for('device_install_batch', device_id=device.id) }}">
<div style="margin-bottom:0.5rem;">
<label style="font-size:0.85rem;cursor:pointer;">
<input type="checkbox" id="select-all-avail" onchange="toggleAllAvail(this)"> Select all
</label>
</div>
<div style="max-height:200px;overflow-y:auto;border:1px solid #cbd5e1;border-radius:4px;padding:0.4rem 0.6rem;">
{% for b in available_batteries %}
<div>
<label style="font-size:0.9rem;cursor:pointer;">
<input type="checkbox" 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>
var FREE_SLOTS = {{ device.battery_slots - device.installed_count() }};
var checkedQueue = [];
document.querySelectorAll('input[name="battery_ids"]').forEach(function(cb) {
cb.addEventListener('change', function() {
if (cb.checked) {
checkedQueue.push(cb);
if (checkedQueue.length > FREE_SLOTS) {
var oldest = checkedQueue.shift();
oldest.checked = false;
}
} else {
checkedQueue = checkedQueue.filter(function(c) { return c !== cb; });
}
document.getElementById('select-all-avail').checked = false;
});
});
function toggleAllAvail(masterCb) {
var all = Array.from(document.querySelectorAll('input[name="battery_ids"]'));
if (masterCb.checked) {
checkedQueue = [];
all.forEach(function(c) { c.checked = false; });
all.slice(0, FREE_SLOTS).forEach(function(c) { c.checked = true; checkedQueue.push(c); });
if (all.length > FREE_SLOTS) masterCb.checked = false;
} else {
all.forEach(function(c) { c.checked = false; });
checkedQueue = [];
}
}
</script>
{% else %}
<p class="text-muted">No compatible batteries available.</p>
{% endif %}
</div>
{% endif %}
<div class="card">
<h2>Logbook</h2>
{% if logbook_entries %}
<div style="display:flex;flex-direction:column;gap:0.6rem;margin-bottom:1rem;">
{% for entry in logbook_entries %}
<div style="border-left:3px solid var(--border);padding:0.35rem 0.75rem;
display:flex;justify-content:space-between;align-items:flex-start;gap:0.5rem;">
<div>
<div class="text-muted" style="font-size:0.78rem;margin-bottom:0.15rem;">{{ entry.recorded_at }}</div>
<div style="white-space:pre-wrap;">{{ entry.body }}</div>
</div>
<form method="post"
action="{{ url_for('device_logbook_delete', device_id=device.id, entry_id=entry.id) }}"
data-confirm="Delete this logbook entry?" data-confirm-ok="Delete">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted" style="margin-bottom:0.75rem;">No logbook entries yet.</p>
{% endif %}
<h3 style="font-size:1rem;margin:0.75rem 0 0.5rem;color:var(--text-h2);">Add Entry</h3>
<form method="post" action="{{ url_for('device_logbook_add', device_id=device.id) }}">
<div class="form-group" style="margin-bottom:0.5rem;">
<textarea name="body" placeholder="Write a note…" rows="2" required style="min-height:60px;"></textarea>
</div>
<button class="btn btn-primary" type="submit">Add Entry</button>
</form>
</div>
<div class="card">
<h2>Edit Device</h2>
<form method="post" action="{{ url_for('device_edit', device_id=device.id) }}">
<div class="form-group">
<label for="edit-name">Name</label>
<input type="text" id="edit-name" name="name" value="{{ device.name }}" required>
</div>
{% if not device.has_children() %}
<div class="form-group">
<label for="edit-slots">Battery Slots</label>
<input type="number" id="edit-slots" name="battery_slots" value="{{ device.battery_slots }}" min="1" required>
</div>
{% endif %}
{% if not device.is_subcomponent() %}
<div class="form-group">
<label>Type</label>
{% set _preset_types = ['Remote Control','Game Controller','Flashlight','Lock','Sensor','Toy','Clock','Smoke Detector'] %}
<select id="edit-device-type-select" onchange="editTypeSelectChanged(this)">
<option value="">— none —</option>
{% for opt in _preset_types %}
<option value="{{ opt }}" {% if device.device_type == opt %}selected{% endif %}>{{ opt }}</option>
{% endfor %}
{% for opt in device_types|default([]) %}
{% if opt not in _preset_types %}
<option value="{{ opt }}" {% if device.device_type == opt %}selected{% endif %}>{{ opt }}</option>
{% endif %}
{% endfor %}
<option value="__new__" {% if device.device_type and device.device_type not in _preset_types and device.device_type not in device_types|default([]) %}selected{% endif %}>Other…</option>
</select>
<input type="text" id="edit-device-type" name="device_type"
value="{{ device.device_type or '' }}"
placeholder="Enter device type"
style="display:{% if device.device_type and device.device_type not in _preset_types %}''{% else %}none{% endif %};margin-top:0.4rem;">
</div>
{% endif %}
{% if not device.has_children() %}
<div class="form-group">
<label>Battery Size</label>
{% set _preset_sizes = ['AA','AAA','C','D','9V','CR2032','CR2025','CR2016','18650','14500','16340','26650','LR44/AG13'] %}
<select id="edit-battery-size-select" onchange="editSizeSelectChanged(this)">
<option value="">— none —</option>
{% for s in _preset_sizes %}
<option value="{{ s }}" {% if device.battery_size == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
{% for s in device_battery_sizes|default([]) %}
{% if s not in _preset_sizes %}
<option value="{{ s }}" {% if device.battery_size == s %}selected{% endif %}>{{ s }}</option>
{% endif %}
{% endfor %}
{% if device.battery_size and device.battery_size not in _preset_sizes and device.battery_size not in device_battery_sizes|default([]) %}
<option value="{{ device.battery_size }}" selected>{{ device.battery_size }}</option>
{% endif %}
<option value="__new__">Other…</option>
</select>
<input type="text" id="edit-battery-size" name="battery_size"
value="{{ device.battery_size or '' }}"
placeholder="e.g. CR123A"
style="display:{% if device.battery_size and device.battery_size not in _preset_sizes %}''{% else %}none{% endif %};margin-top:0.4rem;">
</div>
{% endif %}
{% if not device.is_subcomponent() %}
<div class="form-group">
<label>Location</label>
<select id="edit-location-select" onchange="editLocationSelectChanged(this)">
<option value="">— none —</option>
{% for loc in device_locations|default([]) %}
<option value="{{ loc }}" {% if device.location == loc %}selected{% endif %}>{{ loc }}</option>
{% endfor %}
{% if device.location and device.location not in device_locations|default([]) %}
<option value="{{ device.location }}" selected>{{ device.location }}</option>
{% endif %}
<option value="__new__"> New location…</option>
</select>
<input type="text" id="edit-location" name="location"
value="{{ device.location or '' }}"
placeholder="e.g. Living Room, Bedroom"
style="display:{% if device.location and device.location not in device_locations|default([]) %}''{% else %}none{% endif %};margin-top:0.4rem;">
</div>
{% endif %}
<div class="form-group">
<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() and d.battery_slots == 0 and d.battery_size is none %}
<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>
<div style="position:relative;">
<input type="text" id="edit-ha-entity" name="ha_entity_id"
value="{{ device.ha_entity_id or '' }}"
placeholder="e.g. sensor.tv_remote_battery"
autocomplete="off">
<div id="ha-entity-dropdown"
style="display:none;position:absolute;left:0;right:0;top:100%;z-index:200;
background:var(--bg-card);border:1px solid var(--border);border-radius:4px;
max-height:220px;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,0.15);"></div>
</div>
<small class="text-muted" id="ha-entities-status"
style="display:block;margin-top:0.25rem;font-size:0.8rem;"></small>
</div>
{% endif %}
<button class="btn btn-primary" type="submit">Save Changes</button>
</form>
</div>
<div class="card">
<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>
</form>
</div>
<a class="text-muted" href="{{ url_for('device_list') }}">&larr; Back to Devices</a>
{% if ha_enabled %}
<script>
(function() {
var input = document.getElementById('edit-ha-entity');
var dropdown = document.getElementById('ha-entity-dropdown');
var status = document.getElementById('ha-entities-status');
if (!input || !dropdown) return;
var allEntities = [];
function renderDropdown(query) {
var q = query.toLowerCase();
var matches = q
? allEntities.filter(function(e) {
return e.entity_id.toLowerCase().indexOf(q) !== -1
|| (e.friendly_name && e.friendly_name.toLowerCase().indexOf(q) !== -1);
})
: allEntities;
if (!matches.length) { dropdown.style.display = 'none'; return; }
dropdown.innerHTML = '';
matches.slice(0, 60).forEach(function(e) {
var item = document.createElement('div');
item.style.cssText = 'padding:0.5rem 0.75rem;cursor:pointer;border-bottom:1px solid var(--border);';
var label = document.createElement('div');
label.style.cssText = 'font-size:0.875rem;word-break:break-all;';
label.textContent = e.entity_id;
item.appendChild(label);
if (e.friendly_name && e.friendly_name !== e.entity_id) {
var sub = document.createElement('div');
sub.style.cssText = 'font-size:0.75rem;color:var(--text-muted);';
sub.textContent = e.friendly_name;
item.appendChild(sub);
}
item.addEventListener('mousedown', function(ev) { ev.preventDefault(); });
item.addEventListener('click', function() {
input.value = e.entity_id;
dropdown.style.display = 'none';
input.focus();
});
item.addEventListener('mouseover', function() { this.style.background = 'var(--bg-hover)'; });
item.addEventListener('mouseout', function() { this.style.background = ''; });
dropdown.appendChild(item);
});
dropdown.style.display = 'block';
}
input.addEventListener('input', function() { renderDropdown(this.value); });
input.addEventListener('focus', function() { if (allEntities.length) renderDropdown(this.value); });
input.addEventListener('blur', function() { setTimeout(function() { dropdown.style.display = 'none'; }, 150); });
input.addEventListener('keydown', function(e) { if (e.key === 'Escape') dropdown.style.display = 'none'; });
document.addEventListener('click', function(e) {
if (!input.contains(e.target) && !dropdown.contains(e.target)) dropdown.style.display = 'none';
});
function onEntities(entities) {
allEntities = entities;
if (status) status.textContent = entities.length ? entities.length + ' battery entities available' : '';
}
try {
var cached = sessionStorage.getItem('ha_battery_entities');
if (cached) { onEntities(JSON.parse(cached)); return; }
} catch(e) {}
if (status) status.textContent = 'Loading HA entities\u2026';
fetch('/ha/entities')
.then(function(r) { return r.json(); })
.then(function(entities) {
try { sessionStorage.setItem('ha_battery_entities', JSON.stringify(entities)); } catch(e) {}
onEntities(entities);
})
.catch(function() { if (status) status.textContent = ''; });
}());
</script>
{% endif %}
{% endblock %}