Dashboard UX: auto-submit filter on dropdown change, clickable status cards, clearer status labels
This commit is contained in:
@@ -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},
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user