Files
percoandClaude Sonnet 4.6 cd7c9c4d80 Add plate/color/period filters to events list
New filters on the main page:
- has_plate: "Plaque lue ✓" / "Non lue" toggle
- color: dropdown populated from distinct colors in DB
- date: accepts YYYY (year), YYYY-MM (month), or YYYY-MM-DD (day) — all three formats in a single text input
Pagination links preserve all active filters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 08:43:08 +02:00

166 lines
8.2 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CamWatch — Passages véhicules</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config = { darkMode: 'class' }</script>
<style>
body { background: #0f172a; color: #e2e8f0; }
.card { background: #1e293b; border: 1px solid #334155; }
.plate { font-family: monospace; letter-spacing: 0.12em; }
input, select { background: #0f172a; border: 1px solid #475569; color: #e2e8f0; border-radius: 6px; padding: 6px 10px; }
input:focus, select:focus { outline: none; border-color: #60a5fa; }
select option { background: #0f172a; }
.filter-label { color: #64748b; font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; display: block; margin-bottom: 3px; }
.btn { background: #2563eb; color: #fff; border-radius: 6px; padding: 7px 16px; font-size: 0.85rem; cursor: pointer; }
.btn:hover { background: #1d4ed8; }
.btn-ghost { background: #1e293b; border: 1px solid #475569; color: #94a3b8; }
.btn-ghost:hover { background: #334155; color: #e2e8f0; }
.btn-upload { background: #1e293b; border: 1px solid #475569; color: #94a3b8; border-radius: 6px; padding: 6px 14px; font-size: 0.85rem; cursor: pointer; }
.btn-upload:hover { background: #334155; color: #e2e8f0; }
#upload-panel { display:none; background:#1e293b; border:1px solid #334155; border-radius:10px; padding:16px; margin-bottom:16px; }
#upload-panel.open { display:block; }
#upload-progress { display:none; }
</style>
</head>
<body class="min-h-screen">
<header class="sticky top-0 z-10 px-4 py-3 flex items-center justify-between" style="background:#0f172a;border-bottom:1px solid #1e293b;">
<div class="flex items-center gap-2">
<span class="text-xl">🚗</span>
<span class="font-bold text-lg">CamWatch</span>
<span class="text-slate-500 text-sm hidden sm:inline">— Passages véhicules</span>
</div>
<div class="flex items-center gap-2">
<span class="text-slate-400 text-sm">{{ total }} passage{{ 's' if total != 1 else '' }}</span>
<a href="/stats" class="btn-upload" style="text-decoration:none;">📊 Stats</a>
<a href="/whitelist" class="btn-upload" style="text-decoration:none;">🛡 Whitelist</a>
<a href="/config/zone" class="btn-upload" style="text-decoration:none;">⬡ Zone</a>
<button class="btn-upload" onclick="document.getElementById('upload-panel').classList.toggle('open')">⬆ Tester un clip</button>
</div>
</header>
<main class="max-w-5xl mx-auto px-3 py-4">
<!-- Upload panel -->
<div id="upload-panel">
<p class="text-slate-300 text-sm font-medium mb-3">Tester un clip .mp4 — il sera traité comme un vrai passage (LPR + couleur + frames)</p>
<form id="upload-form" action="/upload" method="post" enctype="multipart/form-data"
onsubmit="startUpload(event)">
<div class="flex items-center gap-3 flex-wrap">
<input type="file" name="file" accept=".mp4,video/mp4" required
class="text-sm text-slate-300 file:mr-3 file:py-1.5 file:px-4 file:rounded file:border-0 file:text-sm file:bg-slate-700 file:text-slate-200 hover:file:bg-slate-600 cursor-pointer">
<button type="submit" class="btn text-sm">Analyser</button>
</div>
</form>
<div id="upload-progress" class="mt-3 text-sm text-slate-400">
⏳ Traitement en cours (extraction frames + LPR)… merci de patienter ~60s
</div>
</div>
<!-- Filters -->
<form method="get" class="mb-5 p-3 rounded-xl flex flex-wrap gap-x-3 gap-y-2 items-end" style="background:#1e293b;border:1px solid #334155;">
<div>
<span class="filter-label">Plaque</span>
<input type="text" name="plate" placeholder="AB-123-CD…" value="{{ filter_plate }}" class="w-28">
</div>
<div>
<span class="filter-label">Détection</span>
<select name="has_plate" class="w-32">
<option value="" {% if not filter_has_plate %}selected{% endif %}>Toutes</option>
<option value="yes" {% if filter_has_plate == 'yes' %}selected{% endif %}>Plaque lue ✓</option>
<option value="no" {% if filter_has_plate == 'no' %}selected{% endif %}>Non lue</option>
</select>
</div>
{% if colors %}
<div>
<span class="filter-label">Couleur</span>
<select name="color" class="w-28">
<option value="" {% if not filter_color %}selected{% endif %}>Toutes</option>
{% for c in colors %}
<option value="{{ c }}" {% if filter_color == c %}selected{% endif %}>{{ c }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div>
<span class="filter-label">Période <span class="normal-case font-normal text-slate-600">(2026 / 2026-06 / 2026-06-04)</span></span>
<input type="text" name="date" placeholder="Année, mois ou jour…" value="{{ filter_date }}" class="w-40">
</div>
<button type="submit" class="btn">Filtrer</button>
{% if filter_plate or filter_date or filter_has_plate or filter_color %}
<a href="/" class="btn btn-ghost">✕ Reset</a>
{% endif %}
</form>
{% if not events %}
<div class="text-center py-20 text-slate-500">
<div class="text-5xl mb-4">📷</div>
<p class="text-lg">Aucun passage enregistré</p>
<p class="text-sm mt-1">En attente de détections véhicules…</p>
</div>
{% endif %}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{% for ev in events %}
<a href="/event/{{ ev.id }}" class="card rounded-xl overflow-hidden block transition-colors hover:border-blue-500" style="text-decoration:none;color:inherit;">
<div class="relative bg-black" style="aspect-ratio:16/9;">
{% if ev.snapshot_path %}
<img src="/{{ ev.snapshot_path }}" alt="snapshot" class="w-full h-full object-cover"
onerror="this.parentElement.innerHTML='<div class=\'w-full h-full flex items-center justify-center text-slate-600 text-4xl\'>📷</div>'">
{% else %}
<div class="w-full h-full flex items-center justify-center text-slate-600 text-4xl">📷</div>
{% endif %}
{% if ev.clip_path %}
<span class="absolute top-2 right-2 text-xs px-1.5 py-0.5 rounded" style="background:rgba(0,0,0,0.7);color:#4ade80;">▶ clip</span>
{% endif %}
</div>
<div class="p-3 space-y-2">
<div class="flex items-center justify-between">
<span class="text-slate-300 text-sm font-medium">{{ ev.time_str }}</span>
<div class="flex items-center gap-1.5">
<div class="w-4 h-4 rounded-full border border-slate-600" style="background:{{ ev.color_hex or '#808080' }};" title="{{ ev.color_name }}"></div>
<span class="text-slate-400 text-xs">{{ ev.color_name or '—' }}</span>
</div>
</div>
<div>
{% if ev.plate %}
<span class="plate text-sm font-bold px-3 py-1 rounded" style="background:#1e3a5f;color:#60a5fa;border:1px solid #2563eb;">{{ ev.plate }}</span>
{% else %}
<span class="text-slate-500 text-sm italic">Plaque non lue</span>
{% endif %}
</div>
</div>
</a>
{% endfor %}
</div>
{% if total_pages > 1 %}
{% set fq = "&plate=" ~ filter_plate ~ "&date=" ~ filter_date ~ "&has_plate=" ~ filter_has_plate ~ "&color=" ~ filter_color %}
<div class="flex items-center justify-center gap-2 mt-6">
{% if page > 1 %}
<a href="?page={{ page - 1 }}{{ fq }}" class="btn btn-ghost text-sm">← Préc.</a>
{% endif %}
<span class="text-slate-400 text-sm">Page {{ page }} / {{ total_pages }}</span>
{% if page < total_pages %}
<a href="?page={{ page + 1 }}{{ fq }}" class="btn btn-ghost text-sm">Suiv. →</a>
{% endif %}
</div>
{% endif %}
</main>
<script>
setTimeout(() => location.reload(), 60000);
function startUpload(e) {
document.getElementById('upload-form').querySelector('button[type=submit]').disabled = true;
document.getElementById('upload-progress').style.display = 'block';
}
</script>
</body>
</html>