diff --git a/backend/services/deployment_history.py b/backend/services/deployment_history.py index 0a5bd27..d9002fc 100644 --- a/backend/services/deployment_history.py +++ b/backend/services/deployment_history.py @@ -138,14 +138,26 @@ def get_deployment_history_data( p.id: p for p in db.query(Project).filter(Project.id.in_(proj_ids)).all() } if proj_ids else {} + # Resolve location names in one batch query (used by the Gantt view + # for per-bar tooltips). + from backend.models import MonitoringLocation + loc_ids = {a.location_id for a in assignments} + loc_name_map = { + l.id: l.name for l in db.query(MonitoringLocation).filter( + MonitoringLocation.id.in_(loc_ids) + ).all() + } if loc_ids else {} + # Compute "active days per project" by walking each assignment and # adding every day in its [start, end] ∩ [first_date, last_date]. # O(N_assignments × avg_window_days); for a typical fleet this is # bounded (hundreds of assignments × hundreds of days = manageable). + # Also collect raw per-assignment bar data for the Gantt view. project_active_days: dict[str, set[date]] = {} project_first_active: dict[str, date] = {} project_last_active: dict[str, date] = {} project_assignment_count: dict[str, int] = {} + project_bars: dict[str, list[dict]] = {} distinct_units: set[str] = set() for a in assignments: @@ -169,6 +181,20 @@ def get_deployment_history_data( if prev_last is None or end > prev_last: project_last_active[a.project_id] = end + # Per-assignment bar data — used by the Gantt view's renderer. + # `is_active` reflects whether the assignment_until was still NULL + # at fetch time (open-ended deployment); the clipped `end` here + # is just for visual bar drawing. + project_bars.setdefault(a.project_id, []).append({ + "unit_id": a.unit_id, + "location_id": a.location_id, + "location_name": loc_name_map.get(a.location_id, "(unknown location)"), + "start": start.isoformat(), + "end": end.isoformat(), + "is_active": a.assigned_until is None, + "source": a.source, + }) + # Build the projects array (sorted by first_active ascending so the # legend reads in deployment-order). projects_data = [] @@ -186,6 +212,7 @@ def get_deployment_history_data( "assignment_count": project_assignment_count.get(pid, 0), "first_active": project_first_active[pid].isoformat() if pid in project_first_active else None, "last_active": project_last_active[pid].isoformat() if pid in project_last_active else None, + "bars": project_bars.get(pid, []), }) continue projects_data.append({ @@ -197,6 +224,7 @@ def get_deployment_history_data( "assignment_count": project_assignment_count.get(pid, 0), "first_active": project_first_active[pid].isoformat() if pid in project_first_active else None, "last_active": project_last_active[pid].isoformat() if pid in project_last_active else None, + "bars": project_bars.get(pid, []), }) projects_data.sort(key=lambda p: (p["first_active"] or "9999", p["name"])) diff --git a/templates/admin/deployment_history.html b/templates/admin/deployment_history.html index b3fdf48..e77d0b0 100644 --- a/templates/admin/deployment_history.html +++ b/templates/admin/deployment_history.html @@ -97,6 +97,18 @@ + +
+ + +
+
@@ -124,6 +136,9 @@ {% endif %}
+ +
+ {% if calendar.projects %}
@@ -211,6 +226,52 @@
{% endif %} +
{# /#dh-view-calendar #} + + + {# /#dh-view-gantt #} +