From 0e3f512203d43b204cd27f2489b3c9d78cfcfed5 Mon Sep 17 00:00:00 2001 From: serversdown Date: Sun, 15 Mar 2026 05:25:23 +0000 Subject: [PATCH] Feat: expands project reservation system. -Reservation list view -expandable project cards --- backend/routers/fleet_calendar.py | 11 +++- templates/fleet_calendar.html | 52 ++++++++++------- .../fleet_calendar/reservations_list.html | 56 ++++++++++--------- 3 files changed, 71 insertions(+), 48 deletions(-) diff --git a/backend/routers/fleet_calendar.py b/backend/routers/fleet_calendar.py index 2127bf7..7603a03 100644 --- a/backend/routers/fleet_calendar.py +++ b/backend/routers/fleet_calendar.py @@ -224,8 +224,9 @@ async def get_reservation( unit_ids = [a.unit_id for a in assignments] units = db.query(RosterUnit).filter(RosterUnit.id.in_(unit_ids)).all() if unit_ids else [] units_by_id = {u.id: u for u in units} - # Build power_type lookup from assignments + # Build power_type and notes lookup from assignments power_type_map = {a.unit_id: a.power_type for a in assignments} + notes_map = {a.unit_id: a.notes for a in assignments} return { "id": reservation.id, @@ -245,7 +246,8 @@ async def get_reservation( "id": uid, "last_calibrated": units_by_id[uid].last_calibrated.isoformat() if uid in units_by_id and units_by_id[uid].last_calibrated else None, "deployed": units_by_id[uid].deployed if uid in units_by_id else False, - "power_type": power_type_map.get(uid) + "power_type": power_type_map.get(uid), + "notes": notes_map.get(uid) } for uid in unit_ids ] @@ -343,6 +345,7 @@ async def assign_units_to_reservation( unit_ids = data.get("unit_ids", []) # Optional per-unit power types: {"BE17354": "ac", "BE9441": "solar"} power_types = data.get("power_types", {}) + location_notes = data.get("location_notes", {}) # Verify units exist (allow empty list to clear all assignments) if unit_ids: @@ -384,7 +387,8 @@ async def assign_units_to_reservation( reservation_id=reservation_id, unit_id=unit_id, assignment_source="filled" if reservation.assignment_type == "quantity" else "specific", - power_type=power_types.get(unit_id) + power_type=power_types.get(unit_id), + notes=location_notes.get(unit_id) ) db.add(assignment) @@ -538,6 +542,7 @@ async def get_reservations_list( { "id": a.unit_id, "power_type": a.power_type, + "notes": a.notes, "deployed": units_by_id[a.unit_id].deployed if a.unit_id in units_by_id else False, "last_calibrated": units_by_id[a.unit_id].last_calibrated if a.unit_id in units_by_id else None, } diff --git a/templates/fleet_calendar.html b/templates/fleet_calendar.html index a5ae29c..a4e3beb 100644 --- a/templates/fleet_calendar.html +++ b/templates/fleet_calendar.html @@ -389,7 +389,7 @@

Active Reservations

Loading reservations...

@@ -417,7 +417,7 @@ -
+

Project Reservations

-
@@ -447,15 +447,18 @@

New Reservation

+ +
+ -
+
-
+
-
+
-
+
{% for color in ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899'] %} @@ -516,12 +519,14 @@
-
+
+
+

Monitoring Locations

@@ -851,17 +856,16 @@ function toggleResCard(id) { const chevron = document.getElementById('chevron-' + id); if (!detail) return; const isHidden = detail.classList.contains('hidden'); - detail.classList.toggle('hidden', !isHidden); + if (isHidden) { + detail.classList.remove('hidden'); + detail.style.display = 'block'; + } else { + detail.classList.add('hidden'); + detail.style.display = 'none'; + } if (chevron) chevron.style.transform = isHidden ? 'rotate(180deg)' : ''; } -// Event delegation for reservation cards (handles HTMX-loaded content) -document.addEventListener('click', function(e) { - const header = e.target.closest('.res-card-header'); - if (!header) return; - const id = header.dataset.resId; - if (id) toggleResCard(id); -}); async function deleteReservation(id, name) { if (!confirm(`Delete reservation "${name}"?\n\nThis will remove all unit assignments.`)) return; @@ -1156,6 +1160,7 @@ function switchPlannerTab(tab) { function switchTab(tab) { document.getElementById('view-calendar').classList.toggle('hidden', tab !== 'calendar'); document.getElementById('view-planner').classList.toggle('hidden', tab !== 'planner'); + if (tab === 'calendar') htmx.trigger(document.body, 'calendar-reservations-refresh'); ['calendar', 'planner'].forEach(t => { const btn = document.getElementById(`tab-btn-${t}`); @@ -1452,6 +1457,7 @@ function plannerReset() { const titleEl = document.getElementById('planner-form-title'); if (titleEl) titleEl.textContent = 'New Reservation'; document.getElementById('planner-save-btn').textContent = 'Save Reservation'; + document.getElementById('planner-meta-fields').style.display = ''; plannerRenderSlots(); plannerRenderUnits(); } @@ -1504,13 +1510,17 @@ async function plannerSave() { // Always call assign-units (even with empty list) — endpoint does a full replace const unitIds = filledSlots.map(s => s.unit_id); const powerTypes = {}; - filledSlots.forEach(s => { if (s.power_type) powerTypes[s.unit_id] = s.power_type; }); + const locationNotes = {}; + filledSlots.forEach(s => { + if (s.power_type) powerTypes[s.unit_id] = s.power_type; + if (s.notes) locationNotes[s.unit_id] = s.notes; + }); const assignResp = await fetch( `/api/fleet-calendar/reservations/${reservationId}/assign-units`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ unit_ids: unitIds, power_types: powerTypes }) + body: JSON.stringify({ unit_ids: unitIds, power_types: powerTypes, location_notes: locationNotes }) } ); const assignResult = await assignResp.json(); @@ -1553,9 +1563,11 @@ async function openPlanner(reservationId) { for (const u of (res.assigned_units || [])) { plannerState.slots.push({ unit_id: u.id, power_type: u.power_type || null, notes: u.notes || null }); } + const titleEl = document.getElementById('planner-form-title'); - if (titleEl) titleEl.textContent = 'Edit: ' + res.name; + if (titleEl) titleEl.textContent = res.name; document.getElementById('planner-save-btn').textContent = 'Save Changes'; + document.getElementById('planner-meta-fields').style.display = 'none'; plannerRenderSlots(); if (res.start_date && res.end_date) plannerLoadUnits(); } catch (e) { diff --git a/templates/partials/fleet_calendar/reservations_list.html b/templates/partials/fleet_calendar/reservations_list.html index ba30d34..8d13ca4 100644 --- a/templates/partials/fleet_calendar/reservations_list.html +++ b/templates/partials/fleet_calendar/reservations_list.html @@ -6,12 +6,13 @@ {% set card_id = "res-card-" ~ res.id %} {% set detail_id = "res-detail-" ~ res.id %} -
+ data-res-id="{{ res.id }}" + onclick="toggleResCard('{{ res.id }}')">
@@ -59,30 +60,30 @@
-
- - - - - + +
@@ -116,23 +117,28 @@

Monitoring Locations

{% for u in item.assigned_units %} -
- Loc. {{ loop.index }} - - - {% if u.power_type == 'ac' %} - A/C - {% elif u.power_type == 'solar' %} - Solar - {% endif %} - {% if u.deployed %} - Deployed - {% else %} - Benched - {% endif %} - {% if u.last_calibrated %} - Cal: {{ u.last_calibrated.strftime('%b %d, %Y') }} +
+
+ Loc. {{ loop.index }} + + + {% if u.power_type == 'ac' %} + A/C + {% elif u.power_type == 'solar' %} + Solar + {% endif %} + {% if u.deployed %} + Deployed + {% else %} + Benched + {% endif %} + {% if u.last_calibrated %} + Cal: {{ u.last_calibrated.strftime('%b %d, %Y') }} + {% endif %} +
+ {% if u.notes %} +

{{ u.notes }}

{% endif %}
{% endfor %}