diff --git a/backend/routers/portal.py b/backend/routers/portal.py index 1cc01c1..1382b45 100644 --- a/backend/routers/portal.py +++ b/backend/routers/portal.py @@ -66,6 +66,26 @@ def active_unit_for_location(location_id: str, db: Session): return asg.unit_id if asg else None +def _client_locations(client: Client, db: Session) -> list: + """The client's active sound locations (for the overview tiles + map).""" + pids = _client_project_ids(client, db) + if not pids: + return [] + projs = {p.id: p.name for p in + db.query(Project.id, Project.name).filter(Project.id.in_(pids)).all()} + locs = (db.query(MonitoringLocation) + .filter(MonitoringLocation.project_id.in_(pids), + MonitoringLocation.location_type == "sound", + MonitoringLocation.removed_at.is_(None)) + .order_by(MonitoringLocation.sort_order, MonitoringLocation.name).all()) + return [{ + "id": loc.id, "name": loc.name, + "address": loc.address, "coordinates": loc.coordinates, + "project_name": projs.get(loc.project_id), + "has_device": active_unit_for_location(loc.id, db) is not None, + } for loc in locs] + + @router.get("/enter/{token}") def portal_enter(token: str, request: Request, db: Session = Depends(get_db)): """Magic-URL entry: validate the token, mint a session cookie, land on /portal.""" @@ -101,14 +121,28 @@ def portal_access(request: Request): @router.get("") -def portal_home(request: Request, client: Client = Depends(get_current_client)): - """Client overview. (M1 task 4 fills in the scoped location list + map.)""" +def portal_home(request: Request, client: Client = Depends(get_current_client), + db: Session = Depends(get_db)): + """Client overview — their active sound locations with live tiles + a map.""" return templates.TemplateResponse( "portal/overview.html", - {"request": request, "client": client, "locations": []}, + {"request": request, "client": client, + "locations": _client_locations(client, db)}, ) +@router.get("/location/{location_id}") +def portal_location(location_id: str, request: Request, + client: Client = Depends(get_current_client), + db: Session = Depends(get_db)): + """Read-only live view for one of the client's locations (404 if not owned).""" + loc = resolve_client_location(client, location_id, db) + return templates.TemplateResponse("portal/location.html", { + "request": request, "client": client, "location": loc, + "has_device": active_unit_for_location(location_id, db) is not None, + }) + + # -- scoped data (cache reads only — never hits the device) ------------------ @router.get("/api/location/{location_id}/live") diff --git a/templates/portal/location.html b/templates/portal/location.html new file mode 100644 index 0000000..4bb107b --- /dev/null +++ b/templates/portal/location.html @@ -0,0 +1,138 @@ +{% extends "portal/base.html" %} +{% block title %}{{ location.name }}{% endblock %} +{% block head %} + +{% endblock %} +{% block content %} +← All locations +
Live sound levels for your active locations.
+Live sound levels for your active locations. Read-only.
-{# M1 task 4 fleshes this out into location tiles + a map. #} {% if locations %} + +