diff --git a/backend/main.py b/backend/main.py index 7accdab..48fc117 100644 --- a/backend/main.py +++ b/backend/main.py @@ -275,6 +275,22 @@ async def tools_page(request: Request): return templates.TemplateResponse("tools.html", {"request": request}) +@app.get("/deploy", response_class=HTMLResponse) +async def deploy_page(request: Request): + """Mobile-first field-capture wizard. Pick a seismograph, snap a + photo of the install, optionally add a memo — drop into the pending + hopper for later classification.""" + return templates.TemplateResponse("deploy.html", {"request": request}) + + +@app.get("/tools/pending-deployments", response_class=HTMLResponse) +async def pending_deployments_page(request: Request): + """List of field captures awaiting classification, plus filters for + historical assigned / cancelled rows. Operators promote a capture + into a real UnitAssignment from here.""" + return templates.TemplateResponse("admin/pending_deployments.html", {"request": request}) + + @app.get("/modems", response_class=HTMLResponse) async def modems_page(request: Request): """Field modems management dashboard""" diff --git a/backend/routers/pending_deployments.py b/backend/routers/pending_deployments.py index 06ae3c8..06cf2e7 100644 --- a/backend/routers/pending_deployments.py +++ b/backend/routers/pending_deployments.py @@ -67,6 +67,50 @@ def _record_history( )) +@router.get("/seismograph-picker") +def seismograph_picker( + q: str = "", + limit: int = 20, + db: Session = Depends(get_db), +): + """JSON list of seismograph units for the /deploy mobile picker. + + Filters out retired units. Sorts by recency of pending captures + first, then alphabetically — so units the operator is actively + deploying with surface at the top. + """ + q_clean = (q or "").strip() + qb = db.query(RosterUnit).filter( + RosterUnit.device_type == "seismograph", + RosterUnit.retired == False, # noqa: E712 + ) + if q_clean: + qb = qb.filter( + (RosterUnit.id.ilike(f"%{q_clean}%")) + | (RosterUnit.note.ilike(f"%{q_clean}%")) + ) + units = qb.order_by(RosterUnit.id).limit(limit).all() + + # Annotate with "has an awaiting pending deployment" so the picker + # can de-emphasize / warn on units that are already mid-deploy. + pending_unit_ids = { + r[0] for r in db.query(PendingDeployment.unit_id) + .filter_by(status="awaiting").distinct().all() + } + + return { + "units": [ + { + "id": u.id, + "note": u.note, + "deployed": u.deployed, + "has_pending": u.id in pending_unit_ids, + } + for u in units + ], + } + + @router.post("/capture") async def capture_deployment( unit_id: str = Form(...), diff --git a/templates/admin/pending_deployments.html b/templates/admin/pending_deployments.html new file mode 100644 index 0000000..2157f52 --- /dev/null +++ b/templates/admin/pending_deployments.html @@ -0,0 +1,462 @@ +{% extends "base.html" %} + +{% block title %}Pending Deployments - Seismo Fleet Manager{% endblock %} + +{% block content %} +
+ Captures from the field waiting to be classified. + Capture a new one → +
++ + captured at + +
+Project type will be Vibration Monitoring.
++ Capture an install while you're still on site. Project + location can be picked later at a desk. +
+Deploying
+—
+Deploying
+—
++ + is now in the pending hopper. + You can classify it from Tools → Pending Deployments later. +
+ +