Dashboard UX: auto-submit filter on dropdown change, clickable status cards, clearer status labels

This commit is contained in:
2026-08-09 15:04:47 +10:00
parent 2945b1c0f6
commit 73f6f291f1
3 changed files with 36 additions and 10 deletions

View File

@@ -76,6 +76,8 @@ def index(request: Request):
GROUP BY source, status ORDER BY source, status"""
).fetchall()
total = conn.execute("SELECT COUNT(*) FROM image_hashes").fetchone()[0]
status_counts = dict(conn.execute(
"SELECT status, COUNT(*) FROM image_hashes GROUP BY status").fetchall())
conn.close()
folders = {}
for name in ("01_keep", "02_review", "03_delete"):
@@ -83,7 +85,7 @@ def index(request: Request):
folders[name] = sum(1 for _ in d.rglob("*") if _.is_file()) if d.exists() else 0
return templates.TemplateResponse(
request, "index.html",
{"rows": rows, "total": total, "folders": folders},
{"rows": rows, "total": total, "folders": folders, "status_counts": status_counts},
)

View File

@@ -38,11 +38,24 @@
</header>
<main>
<div class="cards">
<div class="card-stat"><div class="num">{{ total }}</div><div class="lbl">images fingerprinted</div></div>
<div class="card-stat"><div class="num">{{ folders["01_keep"] }}</div><div class="lbl">in 01_keep</div></div>
<div class="card-stat"><div class="num">{{ folders["02_review"] }}</div><div class="lbl">in 02_review</div></div>
<div class="card-stat"><div class="num">{{ folders["03_delete"] }}</div><div class="lbl">in 03_delete</div></div>
<a href="/review?status=delete_candidate" class="card-stat" style="text-decoration:none;color:inherit;">
<div class="num" style="color:#d43;">{{ status_counts["delete_candidate"] }}</div>
<div class="lbl">⚠ flagged (dark/blurry) — review these →</div>
</a>
<a href="/review?status=keep" class="card-stat" style="text-decoration:none;color:inherit;">
<div class="num">{{ status_counts["keep"] }}</div>
<div class="lbl">keep (clean) — bulk-approve →</div>
</a>
<a href="/review?status=approved" class="card-stat" style="text-decoration:none;color:inherit;">
<div class="num">{{ status_counts["approved"] }}</div>
<div class="lbl">approved (your Keep) →</div>
</a>
<a href="/review?status=rejected" class="card-stat" style="text-decoration:none;color:inherit;">
<div class="num">{{ status_counts["rejected"] }}</div>
<div class="lbl">rejected (your Reject) →</div>
</a>
</div>
<p style="color:#999;font-size:.9rem;">Total fingerprinted: <strong>{{ total }}</strong></p>
<h2>By source & status</h2>
<table>

View File

@@ -56,12 +56,16 @@
<a href="/review?status=approved">Approved</a>
</header>
<div class="filters">
<form method="get" action="/review" style="display:flex;gap:.5rem;" autocomplete="off">
<form method="get" action="/review" style="display:flex;gap:.5rem;" autocomplete="off" id="filter-form">
<button type="submit" style="display:none;">Filter</button>
<select name="status">
<option value="">all statuses</option>
{% for s in ["keep", "review", "delete_candidate", "scanned", "approved", "rejected"] %}
<option value="{{ s }}" {% if status == s %}selected{% endif %}>{{ s }}</option>
{% endfor %}
<option value="">all (every stage)</option>
<option value="delete_candidate" {% if status == "delete_candidate" %}selected{% endif %}>⚠ flagged (dark/blurry) — start here</option>
<option value="keep" {% if status == "keep" %}selected{% endif %}>keep (clean, machine verdict)</option>
<option value="approved" {% if status == "approved" %}selected{% endif %}>approved (your Keep)</option>
<option value="rejected" {% if status == "rejected" %}selected{% endif %}>rejected (your Reject)</option>
<option value="review" {% if status == "review" %}selected{% endif %}>review (reserved)</option>
<option value="scanned" {% if status == "scanned" %}selected{% endif %}>scanned (no verdict yet)</option>
</select>
<input type="text" name="source" value="{{ source or '' }}" placeholder="source (e.g. archive-pictures)">
<button type="submit">Filter</button>
@@ -190,6 +194,13 @@
setTimeout(() => { t.style.display = 'none'; }, 3000);
}
// auto-submit filter when dropdown/input changes (no need to press Filter)
document.getElementById('filter-form').addEventListener('change', e => {
if (e.target.name === 'status' || e.target.name === 'source') {
document.getElementById('filter-form').submit();
}
});
// stash original labels for busy-state restore
document.querySelectorAll('.bulk-bar button').forEach(b => { b.dataset.orig = b.textContent; });