Add device_type field, mobile-friendly improvements, and device filtering

- Device model: add device_type column (String 50, nullable)
- Device add/edit: type select with presets + custom entry
- Device detail: show type in info card; new Edit Device form
- Device list: Type column + client-side filter bar (type + text search)
- Mobile: card-style responsive tables on dashboard and device list,
  form-grid-2col collapse, larger tap targets, stacked form-actions,
  column picker viewport fix, filter bar full-width controls
- Assign page: larger radio touch targets (min-height 44px)
- 3 new acceptance tests for device_type (45 total)
This commit is contained in:
2026-04-12 22:02:29 -05:00
parent b7e2d54bd2
commit 3bc897c1e5
11 changed files with 320 additions and 37 deletions
+56 -7
View File
@@ -379,7 +379,8 @@ def create_app(config_object="config"):
@app.route("/device/")
def device_list():
devices = db.query(Device).order_by(Device.name).all()
return render_template("device_list.html", devices=devices)
device_types = sorted({d.device_type for d in devices if d.device_type})
return render_template("device_list.html", devices=devices, device_types=device_types)
# ------------------------------------------------------------------ #
# Devices — add
@@ -387,14 +388,19 @@ def create_app(config_object="config"):
@app.route("/device/add", methods=["GET", "POST"])
def device_add():
all_devices = db.query(Device).all()
device_types = sorted({d.device_type for d in all_devices if d.device_type})
if request.method == "POST":
name = request.form.get("name", "").strip()
slots_raw = request.form.get("battery_slots", "1").strip()
notes = request.form.get("notes", "").strip() or None
device_type = request.form.get("device_type", "").strip() or None
if not name:
flash("Device name is required.", "error")
return render_template("device_add.html"), 400
return render_template("device_add.html",
device_types=device_types), 400
try:
slots = int(slots_raw)
@@ -403,21 +409,25 @@ def create_app(config_object="config"):
except ValueError:
flash("Battery slots must be a positive integer.", "error")
return render_template("device_add.html",
form_name=name, form_notes=notes or ""), 400
device_types=device_types,
form_name=name, form_notes=notes or "",
form_device_type=request.form.get("device_type", "")), 400
if db.query(Device).filter_by(name=name).first():
flash(f"A device named '{name}' already exists.", "error")
return render_template("device_add.html",
device_types=device_types,
form_name=name, form_slots=slots,
form_notes=notes or ""), 400
form_notes=notes or "",
form_device_type=request.form.get("device_type", "")), 400
device = Device(name=name, battery_slots=slots, notes=notes)
device = Device(name=name, battery_slots=slots, notes=notes, device_type=device_type)
db.add(device)
db.commit()
flash(f"Device '{name}' added.", "success")
return redirect(url_for("device_list"))
return render_template("device_add.html")
return render_template("device_add.html", device_types=device_types)
# ------------------------------------------------------------------ #
# Devices — detail
@@ -432,8 +442,47 @@ def create_app(config_object="config"):
available_batteries = (db.query(Battery)
.filter_by(status="available")
.order_by(Battery.label).all())
device_types = sorted({d.device_type for d in db.query(Device).all() if d.device_type})
return render_template("device_detail.html", device=device, brands=brands,
available_batteries=available_batteries)
available_batteries=available_batteries,
device_types=device_types)
# ------------------------------------------------------------------ #
# Devices — edit
# ------------------------------------------------------------------ #
@app.route("/device/<int:device_id>/edit", methods=["POST"])
def device_edit(device_id):
device = db.get(Device, device_id)
if device is None:
abort(404)
name = request.form.get("name", "").strip()
slots_raw = request.form.get("battery_slots", "1").strip()
notes = request.form.get("notes", "").strip() or None
device_type = request.form.get("device_type", "").strip() or None
if not name:
flash("Device name is required.", "error")
return redirect(url_for("device_detail", device_id=device_id))
try:
slots = int(slots_raw)
if slots < 1:
raise ValueError
except ValueError:
flash("Battery slots must be a positive integer.", "error")
return redirect(url_for("device_detail", device_id=device_id))
existing = db.query(Device).filter_by(name=name).first()
if existing and existing.id != device_id:
flash(f"A device named '{name}' already exists.", "error")
return redirect(url_for("device_detail", device_id=device_id))
device.name = name
device.battery_slots = slots
device.notes = notes
device.device_type = device_type
db.commit()
flash("Device updated.", "success")
return redirect(url_for("device_detail", device_id=device_id))
# ------------------------------------------------------------------ #
# Devices — install batteries