diff --git a/backend/routers/seismo_dashboard.py b/backend/routers/seismo_dashboard.py index fde33b9..72cf7f9 100644 --- a/backend/routers/seismo_dashboard.py +++ b/backend/routers/seismo_dashboard.py @@ -3,13 +3,13 @@ Seismograph Dashboard API Router Provides endpoints for the seismograph-specific dashboard """ -from datetime import date +from datetime import date, datetime, timedelta -from fastapi import APIRouter, Request, Depends, Query +from fastapi import APIRouter, Request, Depends, Query, Form, HTTPException from fastapi.responses import HTMLResponse from sqlalchemy.orm import Session from backend.database import get_db -from backend.models import RosterUnit +from backend.models import RosterUnit, UnitHistory, UserPreferences from backend.templates_config import templates router = APIRouter(prefix="/api/seismo-dashboard", tags=["seismo-dashboard"]) @@ -120,3 +120,109 @@ async def get_seismo_units( "today": date.today() } ) + + +def _get_calibration_interval(db: Session) -> int: + prefs = db.query(UserPreferences).first() + if prefs and prefs.calibration_interval_days: + return prefs.calibration_interval_days + return 365 + + +def _row_context(request: Request, unit: RosterUnit) -> dict: + return {"request": request, "unit": unit, "today": date.today()} + + +@router.get("/unit/{unit_id}/view-row", response_class=HTMLResponse) +async def get_seismo_view_row(unit_id: str, request: Request, db: Session = Depends(get_db)): + unit = db.query(RosterUnit).filter(RosterUnit.id == unit_id).first() + if not unit: + raise HTTPException(status_code=404, detail="Unit not found") + return templates.TemplateResponse("partials/seismo_row_view.html", _row_context(request, unit)) + + +@router.get("/unit/{unit_id}/edit-row", response_class=HTMLResponse) +async def get_seismo_edit_row(unit_id: str, request: Request, db: Session = Depends(get_db)): + unit = db.query(RosterUnit).filter(RosterUnit.id == unit_id).first() + if not unit: + raise HTTPException(status_code=404, detail="Unit not found") + return templates.TemplateResponse("partials/seismo_row_edit.html", _row_context(request, unit)) + + +@router.post("/unit/{unit_id}/quick-update", response_class=HTMLResponse) +async def quick_update_seismo_unit( + unit_id: str, + request: Request, + db: Session = Depends(get_db), + status: str = Form(...), + last_calibrated: str = Form(""), + note: str = Form(""), +): + unit = db.query(RosterUnit).filter(RosterUnit.id == unit_id).first() + if not unit: + raise HTTPException(status_code=404, detail="Unit not found") + + # --- Status --- + old_deployed = unit.deployed + old_out_for_cal = unit.out_for_calibration + if status == "deployed": + unit.deployed = True + unit.out_for_calibration = False + elif status == "out_for_calibration": + unit.deployed = False + unit.out_for_calibration = True + else: + unit.deployed = False + unit.out_for_calibration = False + + if unit.deployed != old_deployed or unit.out_for_calibration != old_out_for_cal: + old_status = "deployed" if old_deployed else ("out_for_calibration" if old_out_for_cal else "benched") + db.add(UnitHistory( + unit_id=unit_id, + change_type="deployed_change", + field_name="status", + old_value=old_status, + new_value=status, + source="manual", + )) + + # --- Last calibrated --- + old_cal = unit.last_calibrated + if last_calibrated: + try: + new_cal = datetime.strptime(last_calibrated, "%Y-%m-%d").date() + except ValueError: + raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD") + unit.last_calibrated = new_cal + unit.next_calibration_due = new_cal + timedelta(days=_get_calibration_interval(db)) + else: + unit.last_calibrated = None + unit.next_calibration_due = None + + if unit.last_calibrated != old_cal: + db.add(UnitHistory( + unit_id=unit_id, + change_type="calibration_status_change", + field_name="last_calibrated", + old_value=old_cal.strftime("%Y-%m-%d") if old_cal else None, + new_value=last_calibrated or None, + source="manual", + )) + + # --- Note --- + old_note = unit.note + unit.note = note or None + if unit.note != old_note: + db.add(UnitHistory( + unit_id=unit_id, + change_type="note_change", + field_name="note", + old_value=old_note, + new_value=unit.note, + source="manual", + )) + + db.commit() + db.refresh(unit) + + return templates.TemplateResponse("partials/seismo_row_view.html", _row_context(request, unit)) diff --git a/templates/partials/seismo_row_edit.html b/templates/partials/seismo_row_edit.html new file mode 100644 index 0000000..dc0945d --- /dev/null +++ b/templates/partials/seismo_row_edit.html @@ -0,0 +1,63 @@ + + + + {{ unit.id }} + + + + + + + {% if unit.deployed_with_modem_id %} + + {{ unit.deployed_with_modem_id }} + + {% else %} + None + {% endif %} + + + {% if unit.address %} + {{ unit.address }} + {% elif unit.coordinates %} + {{ unit.coordinates }} + {% else %} + + {% endif %} + + + + + + + + +
+ + +
+ + diff --git a/templates/partials/seismo_row_view.html b/templates/partials/seismo_row_view.html new file mode 100644 index 0000000..f41abdd --- /dev/null +++ b/templates/partials/seismo_row_view.html @@ -0,0 +1,93 @@ + + + + {{ unit.id }} + + + + {% if unit.deployed %} + + + + + Deployed + + {% elif unit.out_for_calibration %} + + + + + Out for Cal + + {% else %} + + + + + Benched + + {% endif %} + + + {% if unit.deployed_with_modem_id %} + + {{ unit.deployed_with_modem_id }} + + {% else %} + None + {% endif %} + + + {% if unit.address %} + {{ unit.address }} + {% elif unit.coordinates %} + {{ unit.coordinates }} + {% else %} + + {% endif %} + + + {% if unit.last_calibrated %} + + {% if unit.next_calibration_due and today %} + {% set days_until = (unit.next_calibration_due - today).days %} + {% if days_until < 0 %} + + {% elif days_until <= 14 %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + {{ unit.last_calibrated.strftime('%Y-%m-%d') }} + + {% else %} + + {% endif %} + + + {% if unit.note %} + {{ unit.note }} + {% else %} + + {% endif %} + + +
+ + + View Details → + +
+ + diff --git a/templates/partials/seismo_unit_list.html b/templates/partials/seismo_unit_list.html index 09cf087..2611984 100644 --- a/templates/partials/seismo_unit_list.html +++ b/templates/partials/seismo_unit_list.html @@ -92,88 +92,7 @@ {% for unit in units %} - - - - {{ unit.id }} - - - - {% if unit.deployed %} - - - - - Deployed - - {% elif unit.out_for_calibration %} - - - - - Out for Cal - - {% else %} - - - - - Benched - - {% endif %} - - - {% if unit.deployed_with_modem_id %} - - {{ unit.deployed_with_modem_id }} - - {% else %} - None - {% endif %} - - - {% if unit.address %} - {{ unit.address }} - {% elif unit.coordinates %} - {{ unit.coordinates }} - {% else %} - - {% endif %} - - - {% if unit.last_calibrated %} - - {% if unit.next_calibration_due and today %} - {% set days_until = (unit.next_calibration_due - today).days %} - {% if days_until < 0 %} - - {% elif days_until <= 14 %} - - {% else %} - - {% endif %} - {% else %} - - {% endif %} - {{ unit.last_calibrated.strftime('%Y-%m-%d') }} - - {% else %} - - {% endif %} - - - {% if unit.note %} - {{ unit.note }} - {% else %} - - {% endif %} - - - - View Details → - - - + {% include "partials/seismo_row_view.html" %} {% endfor %}