feat(deployments): mobile capture wizard + classify hopper + dashboard banner

UI for the pending-deployment workflow (commits 2 + 3 from the plan,
landed together since commit 1 already shipped the full backend).

New surfaces
- /deploy — mobile-first 3-step wizard.  Pick unit → take photo (uses
  <input capture="environment"> so it opens the phone camera) → add
  optional note + submit.  EXIF GPS auto-extracted on the server.
  Success page shows the captured coords + links to either "Deploy
  another" or "View pending hopper."  Whole flow is meant to take
  under 90 seconds on site.
- /tools/pending-deployments — the hopper.  Filter pills: Awaiting /
  Assigned / Cancelled.  Each card shows photo thumbnail, unit serial
  link, captured-at timestamp, coordinates, operator note, and
  status-appropriate actions.
- Classify modal on the hopper: two modes — "Assign to existing
  location" (project + location pickers, scoped to vibration_monitoring)
  or "Create new location" (with new-or-existing project, plus a
  "use captured coords" checkbox that writes the pending row's coords
  onto the new location).  Calls /pending/{id}/promote on submit.
- Cancel button uses prompt() for the optional reason → POSTs to
  /pending/{id}/cancel.

Backend additions
- GET /api/deployments/seismograph-picker — JSON list of non-retired
  seismograph units for the /deploy unit picker.  Annotates each unit
  with has_pending so the picker can flag units that already have a
  pending capture waiting.

Discovery
- New "Field Deploy" + "Pending Deployments" cards on /tools.
- Dashboard banner: auto-shows when there are awaiting captures,
  polled every 30s.  Hides when count drops to 0.  Click → /tools/
  pending-deployments.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 03:45:18 +00:00
parent e05f2189c4
commit 1af5a94f57
6 changed files with 906 additions and 0 deletions
+37
View File
@@ -14,6 +14,43 @@
<!-- Card grid -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Field Deploy (mobile-first) -->
<a href="/deploy"
class="block bg-white dark:bg-slate-800 rounded-xl shadow-lg p-5 hover:shadow-xl transition-shadow border border-transparent hover:border-seismo-orange">
<div class="flex items-start gap-3">
<div class="w-10 h-10 rounded-lg bg-orange-100 dark:bg-orange-900/30 text-seismo-orange flex items-center justify-center shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 dark:text-white">Field Deploy</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
On site? Pick a unit, snap an install photo, leave. GPS is auto-captured. Classify the project/location later from a desk.
</p>
</div>
</div>
</a>
<!-- Pending Deployments (the hopper) -->
<a href="/tools/pending-deployments"
class="block bg-white dark:bg-slate-800 rounded-xl shadow-lg p-5 hover:shadow-xl transition-shadow border border-transparent hover:border-seismo-orange">
<div class="flex items-start gap-3">
<div class="w-10 h-10 rounded-lg bg-amber-100 dark:bg-amber-900/30 text-amber-600 dark:text-amber-400 flex items-center justify-center shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
</svg>
</div>
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-gray-900 dark:text-white">Pending Deployments</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Field captures waiting to be classified into a project + location.
</p>
</div>
</div>
</a>
<!-- Pair Devices -->
<a href="/pair-devices"
class="block bg-white dark:bg-slate-800 rounded-xl shadow-lg p-5 hover:shadow-xl transition-shadow border border-transparent hover:border-seismo-orange">