39b52a3fa4
- base.html: add CSS/HTML/JS for styled in-app confirmation modal (dark-mode compatible via CSS vars) - device_list, battery_detail: convert onsubmit confirm() to declarative data-confirm attributes - dashboard: convert bulk Delete/Install buttons to use modal helpers (submitWithAction pattern) - app.py: pass brand_counts dict to battery_add template - battery_add.html: show live "Will create: Brand 001 → Brand 003" preview as brand/quantity change - tests: add two tests covering brand_counts server-side rendering
149 lines
5.7 KiB
HTML
149 lines
5.7 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Add Batteries — Battery Tracker{% endblock %}
|
||
|
||
{% block content %}
|
||
<h1>Add Batteries</h1>
|
||
|
||
<div class="card">
|
||
<form method="post" action="{{ url_for('battery_add') }}">
|
||
|
||
<div class="form-grid-2col" style="display:grid;grid-template-columns:1fr 1fr;gap:0 1rem;">
|
||
|
||
<div class="form-group" style="grid-column:1/-1;">
|
||
<label for="brand-select">Brand <span class="text-danger">*</span></label>
|
||
<select id="brand-select" onchange="metaSelectChanged(this, 'brand')">
|
||
<option value="">— select a brand —</option>
|
||
{% for b in brands|default([]) %}
|
||
<option value="{{ b }}">{{ b }}</option>
|
||
{% endfor %}
|
||
<option value="__new__">➕ New brand…</option>
|
||
</select>
|
||
<input type="text" id="brand" name="brand" value="{{ form_brand|default('') }}"
|
||
placeholder="Type new brand name" required
|
||
style="display:none;margin-top:0.4rem;">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="count">Quantity</label>
|
||
<input type="number" id="count" name="count" value="{{ form_count|default(1) }}" min="1" max="50">
|
||
<small class="text-muted">Labels are auto-generated (e.g. Eneloop 001, Eneloop 002)</small>
|
||
<div id="label-preview" style="font-size:0.85rem;color:var(--link);margin-top:0.3rem;font-weight:500;min-height:1.2em;"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="capacity_mah">Capacity (mAh)</label>
|
||
<input type="number" id="capacity_mah" name="capacity_mah" min="0"
|
||
value="" placeholder="e.g. 2000 — optional">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Size</label>
|
||
<select id="size-select" onchange="metaSelectChanged(this,'size')">
|
||
<option value="">— none —</option>
|
||
{% for opt in ['AA','AAA','C','D','9V','18650','21700','14500','26650','CR2032','CR123A'] %}
|
||
<option value="{{ opt }}">{{ opt }}</option>
|
||
{% endfor %}
|
||
<option value="__new__">Other…</option>
|
||
</select>
|
||
<input type="text" id="size" name="size" value=""
|
||
placeholder="Enter size" style="display:none;margin-top:0.4rem;">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Chemistry</label>
|
||
<select id="chemistry-select" onchange="metaSelectChanged(this,'chemistry')">
|
||
<option value="">— none —</option>
|
||
{% for opt in ['NiMH','Alkaline','Li-ion','LiFePO4','NiCd','Zinc-Carbon','Li-MnO2'] %}
|
||
<option value="{{ opt }}">{{ opt }}</option>
|
||
{% endfor %}
|
||
<option value="__new__">Other…</option>
|
||
</select>
|
||
<input type="text" id="chemistry" name="chemistry" value=""
|
||
placeholder="Enter chemistry" style="display:none;margin-top:0.4rem;">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="purchase_date">Purchase Date</label>
|
||
<input type="date" id="purchase_date" name="purchase_date" value="">
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Storage Location</label>
|
||
<select id="storage-select" onchange="metaSelectChanged(this,'storage_location')">
|
||
<option value="">— none —</option>
|
||
{% for loc in storage_locations|default([]) %}
|
||
<option value="{{ loc }}">{{ loc }}</option>
|
||
{% endfor %}
|
||
<option value="__new__">➕ New location…</option>
|
||
</select>
|
||
<input type="text" id="storage_location" name="storage_location" value=""
|
||
placeholder="e.g. Drawer 2, Toolbox, Shelf A"
|
||
style="display:none;margin-top:0.4rem;">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="notes">Notes</label>
|
||
<textarea id="notes" name="notes"
|
||
placeholder="Optional notes applied to all batteries…">{{ form_notes|default('') }}</textarea>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button class="btn btn-primary" type="submit">Add Batteries</button>
|
||
<a class="btn btn-secondary" href="{{ url_for('dashboard') }}">Cancel</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
var brandCounts = {{ brand_counts|default({})|tojson }};
|
||
|
||
function metaSelectChanged(sel, inputId) {
|
||
var input = document.getElementById(inputId);
|
||
if (sel.value === '__new__') {
|
||
input.style.display = '';
|
||
input.value = '';
|
||
input.focus();
|
||
} else {
|
||
input.style.display = 'none';
|
||
input.value = sel.value;
|
||
}
|
||
if (inputId === 'brand') updateLabelPreview();
|
||
}
|
||
|
||
function updateLabelPreview() {
|
||
var brand = document.getElementById('brand').value.trim();
|
||
var count = parseInt(document.getElementById('count').value, 10) || 1;
|
||
var preview = document.getElementById('label-preview');
|
||
if (!brand) { preview.textContent = ''; return; }
|
||
var existing = brandCounts[brand] !== undefined ? brandCounts[brand] : 0;
|
||
var first = existing + 1;
|
||
var last = existing + count;
|
||
var pad = function(n) { return n.toString().padStart(3, '0'); };
|
||
preview.textContent = count === 1
|
||
? 'Will create: ' + brand + ' ' + pad(first)
|
||
: 'Will create: ' + brand + ' ' + pad(first) + ' \u2192 ' + brand + ' ' + pad(last);
|
||
}
|
||
|
||
document.getElementById('count').addEventListener('input', updateLabelPreview);
|
||
document.getElementById('brand').addEventListener('input', updateLabelPreview);
|
||
|
||
// Restore brand state on error re-render
|
||
(function () {
|
||
var input = document.getElementById('brand');
|
||
var sel = document.getElementById('brand-select');
|
||
if (!input.value) return;
|
||
for (var i = 0; i < sel.options.length; i++) {
|
||
if (sel.options[i].value === input.value) {
|
||
sel.value = input.value;
|
||
input.style.display = 'none';
|
||
return;
|
||
}
|
||
}
|
||
sel.value = '__new__';
|
||
input.style.display = '';
|
||
}());
|
||
</script>
|
||
{% endblock %}
|