diff --git a/backend/main.py b/backend/main.py index 6ef6ca6..488430a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -233,8 +233,11 @@ async def nrl_detail_page( # Get session count session_count = db.query(RecordingSession).filter_by(location_id=location_id).count() - # Get file count - file_count = db.query(DataFile).filter_by(location_id=location_id).count() + # Get file count (DataFile links to session, not directly to location) + file_count = db.query(DataFile).join( + RecordingSession, + DataFile.session_id == RecordingSession.id + ).filter(RecordingSession.location_id == location_id).count() # Check for active session active_session = db.query(RecordingSession).filter( diff --git a/backend/routers/project_locations.py b/backend/routers/project_locations.py index 8d63a0c..801e21a 100644 --- a/backend/routers/project_locations.py +++ b/backend/routers/project_locations.py @@ -460,8 +460,12 @@ async def get_nrl_files( """ from backend.models import DataFile, RecordingSession - files = db.query(DataFile).filter_by( - location_id=location_id + # Join DataFile with RecordingSession to filter by location_id + files = db.query(DataFile).join( + RecordingSession, + DataFile.session_id == RecordingSession.id + ).filter( + RecordingSession.location_id == location_id ).order_by(DataFile.created_at.desc()).all() # Enrich with session details diff --git a/backend/routers/projects.py b/backend/routers/projects.py index 7701982..5a72362 100644 --- a/backend/routers/projects.py +++ b/backend/routers/projects.py @@ -341,6 +341,230 @@ async def get_project_dashboard( }) +# ============================================================================ +# Project Types +# ============================================================================ + +@router.get("/{project_id}/header", response_class=JSONResponse) +async def get_project_header(project_id: str, db: Session = Depends(get_db)): + """ + Get project header information for dynamic display. + Returns JSON with project name, status, and type. + """ + project = db.query(Project).filter_by(id=project_id).first() + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + project_type = db.query(ProjectType).filter_by(id=project.project_type_id).first() + + return JSONResponse({ + "id": project.id, + "name": project.name, + "status": project.status, + "project_type_id": project.project_type_id, + "project_type_name": project_type.name if project_type else None, + }) + + +@router.get("/{project_id}/units", response_class=HTMLResponse) +async def get_project_units( + project_id: str, + request: Request, + db: Session = Depends(get_db), +): + """ + Get all units assigned to this project's locations. + Returns HTML partial with unit list. + """ + from backend.models import DataFile + + # Get all assignments for this project + assignments = db.query(UnitAssignment).filter( + and_( + UnitAssignment.project_id == project_id, + UnitAssignment.status == "active", + ) + ).all() + + # Enrich with unit and location details + units_data = [] + for assignment in assignments: + unit = db.query(RosterUnit).filter_by(id=assignment.unit_id).first() + location = db.query(MonitoringLocation).filter_by(id=assignment.location_id).first() + + # Count sessions for this assignment + session_count = db.query(func.count(RecordingSession.id)).filter_by( + location_id=assignment.location_id, + unit_id=assignment.unit_id, + ).scalar() + + # Count files from sessions + file_count = db.query(func.count(DataFile.id)).join( + RecordingSession, + DataFile.session_id == RecordingSession.id + ).filter( + RecordingSession.location_id == assignment.location_id, + RecordingSession.unit_id == assignment.unit_id, + ).scalar() + + # Check if currently recording + active_session = db.query(RecordingSession).filter( + and_( + RecordingSession.location_id == assignment.location_id, + RecordingSession.unit_id == assignment.unit_id, + RecordingSession.status == "recording", + ) + ).first() + + units_data.append({ + "assignment": assignment, + "unit": unit, + "location": location, + "session_count": session_count, + "file_count": file_count, + "active_session": active_session, + }) + + # Get project type for label context + project = db.query(Project).filter_by(id=project_id).first() + project_type = db.query(ProjectType).filter_by(id=project.project_type_id).first() if project else None + + return templates.TemplateResponse("partials/projects/unit_list.html", { + "request": request, + "project_id": project_id, + "units": units_data, + "project_type": project_type, + }) + + +@router.get("/{project_id}/schedules", response_class=HTMLResponse) +async def get_project_schedules( + project_id: str, + request: Request, + db: Session = Depends(get_db), + status: Optional[str] = Query(None), +): + """ + Get scheduled actions for this project. + Returns HTML partial with schedule list. + Optional status filter: pending, completed, failed, cancelled + """ + query = db.query(ScheduledAction).filter_by(project_id=project_id) + + # Filter by status if provided + if status: + query = query.filter(ScheduledAction.execution_status == status) + + schedules = query.order_by(ScheduledAction.scheduled_time.desc()).all() + + # Enrich with location details + schedules_data = [] + for schedule in schedules: + location = None + if schedule.location_id: + location = db.query(MonitoringLocation).filter_by(id=schedule.location_id).first() + + schedules_data.append({ + "schedule": schedule, + "location": location, + }) + + return templates.TemplateResponse("partials/projects/schedule_list.html", { + "request": request, + "project_id": project_id, + "schedules": schedules_data, + }) + + +@router.get("/{project_id}/sessions", response_class=HTMLResponse) +async def get_project_sessions( + project_id: str, + request: Request, + db: Session = Depends(get_db), + status: Optional[str] = Query(None), +): + """ + Get all recording sessions for this project. + Returns HTML partial with session list. + Optional status filter: recording, completed, paused, failed + """ + query = db.query(RecordingSession).filter_by(project_id=project_id) + + # Filter by status if provided + if status: + query = query.filter(RecordingSession.status == status) + + sessions = query.order_by(RecordingSession.started_at.desc()).all() + + # Enrich with unit and location details + sessions_data = [] + for session in sessions: + unit = None + location = None + + if session.unit_id: + unit = db.query(RosterUnit).filter_by(id=session.unit_id).first() + if session.location_id: + location = db.query(MonitoringLocation).filter_by(id=session.location_id).first() + + sessions_data.append({ + "session": session, + "unit": unit, + "location": location, + }) + + return templates.TemplateResponse("partials/projects/session_list.html", { + "request": request, + "project_id": project_id, + "sessions": sessions_data, + }) + + +@router.get("/{project_id}/files", response_class=HTMLResponse) +async def get_project_files( + project_id: str, + request: Request, + db: Session = Depends(get_db), + file_type: Optional[str] = Query(None), +): + """ + Get all data files from all sessions in this project. + Returns HTML partial with file list. + Optional file_type filter: audio, data, log, etc. + """ + from backend.models import DataFile + + # Join through RecordingSession to get project files + query = db.query(DataFile).join( + RecordingSession, + DataFile.session_id == RecordingSession.id + ).filter(RecordingSession.project_id == project_id) + + # Filter by file type if provided + if file_type: + query = query.filter(DataFile.file_type == file_type) + + files = query.order_by(DataFile.created_at.desc()).all() + + # Enrich with session details + files_data = [] + for file in files: + session = None + if file.session_id: + session = db.query(RecordingSession).filter_by(id=file.session_id).first() + + files_data.append({ + "file": file, + "session": session, + }) + + return templates.TemplateResponse("partials/projects/file_list.html", { + "request": request, + "project_id": project_id, + "files": files_data, + }) + + # ============================================================================ # Project Types # ============================================================================ diff --git a/templates/partials/projects/file_list.html b/templates/partials/projects/file_list.html index f346e42..103a094 100644 --- a/templates/partials/projects/file_list.html +++ b/templates/partials/projects/file_list.html @@ -34,7 +34,7 @@
+ {{ item.schedule.description }} +
+ {% endif %} + + {% if item.schedule.result_message %} +No scheduled actions yet
+Create schedules to automate tasks
++ {{ item.unit.note }} +
+ {% endif %} +No units assigned yet
+Assign units to locations to get started
+Sound monitoring project overview and assignments
+ +Update project details and status
-